crowdin-cli 0.0.2
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.
- data/README.rdoc +21 -0
- data/bin/crowdin-cli +337 -0
- data/crowdin-cli.rdoc +5 -0
- data/lib/crowdin-cli.rb +6 -0
- data/lib/crowdin-cli/version.rb +5 -0
- metadata +154 -0
data/README.rdoc
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
= crowdin-cli
|
2
|
+
|
3
|
+
Describe your project here
|
4
|
+
|
5
|
+
== Configuration
|
6
|
+
Create a `.crowdin` YAML file in your root project directory with the following structure:
|
7
|
+
|
8
|
+
```yaml
|
9
|
+
---
|
10
|
+
project_id: ''
|
11
|
+
api_key: ''
|
12
|
+
server: 'http://api.crowdin.net'
|
13
|
+
|
14
|
+
files:
|
15
|
+
-
|
16
|
+
source-file-path: locale/en/LC_MESSAGES/messages.po
|
17
|
+
translations: locale/<two-letters-code>/LC_MESSAGES/<original-file-name>
|
18
|
+
```
|
19
|
+
|
20
|
+
:include:crowdin-cli.rdoc
|
21
|
+
|
data/bin/crowdin-cli
ADDED
@@ -0,0 +1,337 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'pp'
|
4
|
+
require 'json'
|
5
|
+
require 'gli'
|
6
|
+
require 'zip/zip'
|
7
|
+
require 'crowdin-api'
|
8
|
+
require 'crowdin-cli'
|
9
|
+
|
10
|
+
#begin # XXX: Remove this begin/rescue before distributing your app
|
11
|
+
#require 'crowdin-cli'
|
12
|
+
#rescue LoadError
|
13
|
+
# STDERR.puts "In development, you need to use `bundle exec bin/crowdin-cli` to run your app"
|
14
|
+
# STDERR.puts "At install-time, RubyGems will make sure lib, etc. are in the load path"
|
15
|
+
# STDERR.puts "Feel free to remove this message from bin/crowdin-cli now"
|
16
|
+
# exit 64
|
17
|
+
#end
|
18
|
+
|
19
|
+
# Ця хуїта повертає структуру директорій на сервері
|
20
|
+
#
|
21
|
+
def walk_remote_tree(files, root = '/', result = { dirs: [], files: [] })
|
22
|
+
files.each do |node|
|
23
|
+
case node['node_type']
|
24
|
+
when 'directory'
|
25
|
+
result[:dirs] << "#{root}#{node['name']}"
|
26
|
+
walk_remote_tree(node['files'], root + node['name'] + '/', result)
|
27
|
+
when 'file'
|
28
|
+
result[:files] << "#{root}#{node['name']}"
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
return result
|
33
|
+
end
|
34
|
+
|
35
|
+
|
36
|
+
# Хуйня... хулі
|
37
|
+
# Повертає локальну структуру директорій
|
38
|
+
# На вході масив файлів, наприклад:
|
39
|
+
# ['/path/to/admin/en.xml', '/path/to/user/settings/strings.xml']
|
40
|
+
# Результат хеш { dirs:[послідовний масив директорій для створення], files: [] }
|
41
|
+
#
|
42
|
+
def walk_local_tree(files, result = { dirs: [], files: [] })
|
43
|
+
result[:files] = files
|
44
|
+
|
45
|
+
files = files.inject([]){ |res, a|
|
46
|
+
res << a.split('/').drop(1).inject([]){ |res, s|
|
47
|
+
res << res.last.to_s + '/' + s
|
48
|
+
}
|
49
|
+
}
|
50
|
+
# Ex: files = [["/path", "/path/to", "/path/to/admin", "/path/to/admin/en.xml"], ... ]
|
51
|
+
|
52
|
+
files.map(&:pop) # delete last element from each array
|
53
|
+
result[:dirs] = files.flatten.uniq
|
54
|
+
|
55
|
+
return result
|
56
|
+
end
|
57
|
+
|
58
|
+
def unzip_file(file, dest)
|
59
|
+
# overwrite files if they already exist inside of the extracted path
|
60
|
+
Zip.options[:on_exists_proc] = true
|
61
|
+
|
62
|
+
Zip::ZipFile.open(file) do |zip_file|
|
63
|
+
zip_file.each do |f|
|
64
|
+
f_path = File.join(dest, f.name)
|
65
|
+
FileUtils.mkdir_p(File.dirname(f_path))
|
66
|
+
puts "Download: #{f}"
|
67
|
+
zip_file.extract(f, f_path)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
|
73
|
+
###
|
74
|
+
include GLI::App
|
75
|
+
|
76
|
+
program_desc 'A CLI to sync locale files with crowdin.net'
|
77
|
+
|
78
|
+
version Crowdin::CLI::VERSION
|
79
|
+
|
80
|
+
desc 'Be verbose'
|
81
|
+
switch [:v, :verbose]
|
82
|
+
|
83
|
+
desc 'Path to config file'
|
84
|
+
default_value File.join(Dir.pwd, 'crowdin.yaml')
|
85
|
+
arg_name '<s>'
|
86
|
+
flag [:c, :config]
|
87
|
+
|
88
|
+
|
89
|
+
desc 'Upload existing translations to Crowdin project'
|
90
|
+
#arg_name 'Describe arguments to upload here'
|
91
|
+
command :upload do |c|
|
92
|
+
# Command 'upload' requires a subcommand
|
93
|
+
#
|
94
|
+
#c.action do |global_options, options, args|
|
95
|
+
# puts "upload command ran"
|
96
|
+
#end
|
97
|
+
|
98
|
+
#c.default_command :all
|
99
|
+
|
100
|
+
#c.desc 'Upload source and translation files'
|
101
|
+
#c.command :all do |c|
|
102
|
+
# c.action do |global_options, options, args|
|
103
|
+
# puts options
|
104
|
+
# puts "`upload all` command ran"
|
105
|
+
# end
|
106
|
+
#end
|
107
|
+
|
108
|
+
# TODO: change variables names!
|
109
|
+
c.desc 'Upload source files'
|
110
|
+
c.command :sources do |c|
|
111
|
+
c.action do |global_options, options, args|
|
112
|
+
|
113
|
+
c.desc 'defines whether to add files if there is the same file previously added'
|
114
|
+
c.switch [:upload_duplicates]
|
115
|
+
|
116
|
+
project_info = @crowdin.project_info(:json)
|
117
|
+
project_info = JSON.parse(project_info)
|
118
|
+
|
119
|
+
remote_project_tree = walk_remote_tree(project_info['files'])
|
120
|
+
|
121
|
+
local_files = []
|
122
|
+
@config['files'].each do |file|
|
123
|
+
local_file = "#{@local_path}#{@sources_root}#{file['source']}"
|
124
|
+
|
125
|
+
if File.exist?(local_file)
|
126
|
+
local_files << { dest: file['source'], source: local_file, export_pattern: file['translation'] }
|
127
|
+
else
|
128
|
+
Dir.glob(local_file).each do |f|
|
129
|
+
local_files << { dest: f.sub("#{@local_path}#{@sources_root}", ''), source: f, export_pattern: file['translation'] }
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
local_project_tree = walk_local_tree(local_files.collect{ |h| h[:dest] })
|
135
|
+
|
136
|
+
#=begin
|
137
|
+
# Create directory tree
|
138
|
+
#
|
139
|
+
create_dirs = local_project_tree[:dirs] - remote_project_tree[:dirs]
|
140
|
+
create_dirs.each do |dir|
|
141
|
+
puts "Create directory `#{dir}`"
|
142
|
+
@crowdin.add_directory(dir)
|
143
|
+
end
|
144
|
+
|
145
|
+
# Update existing files in Crowdin project
|
146
|
+
#
|
147
|
+
# array containing elements common to the two arrays
|
148
|
+
if options[:upload_dublicates]
|
149
|
+
update_files = local_project_tree[:files] & remote_project_tree[:files]
|
150
|
+
files_for_upload = local_files.select{ |file| update_files.include?(file[:dest]) }
|
151
|
+
files_for_upload.map{ |file| file.keep_if{ |k, v| [:source, :dest].include?(k) } }
|
152
|
+
files_for_upload.each do |file|
|
153
|
+
puts "Update file `#{file[:dest]}`"
|
154
|
+
@crowdin.update_file([] << file)
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
# Add new files to Crowdin project
|
159
|
+
#
|
160
|
+
add_files = local_project_tree[:files] - remote_project_tree[:files]
|
161
|
+
files_for_add = local_files.select{ |file| add_files.include?(file[:dest]) }
|
162
|
+
files_for_add.each do |file|
|
163
|
+
puts "Add new file `#{file[:dest]}`"
|
164
|
+
@crowdin.add_file([] << file)
|
165
|
+
end
|
166
|
+
#=end
|
167
|
+
|
168
|
+
end # action
|
169
|
+
end # command
|
170
|
+
|
171
|
+
|
172
|
+
c.desc 'Upload translation files'
|
173
|
+
c.command :translations do |c|
|
174
|
+
|
175
|
+
c.desc 'the language of translation you need'
|
176
|
+
c.default_value 'all'
|
177
|
+
c.arg_name 'language_code'
|
178
|
+
c.flag [:l, :language]
|
179
|
+
|
180
|
+
c.desc 'defines whether to add translation if there is the same translation previously added'
|
181
|
+
c.switch [:import_duplicates]
|
182
|
+
|
183
|
+
c.desc 'defines whether to add translation if it is equal to source string at Crowdin'
|
184
|
+
c.switch [:import_eq_suggestions]
|
185
|
+
|
186
|
+
c.desc 'mark uploaded translations as approved'
|
187
|
+
c.switch [:auto_approve_imported]
|
188
|
+
|
189
|
+
|
190
|
+
c.action do |global_options, options, args|
|
191
|
+
language = options[:language]
|
192
|
+
|
193
|
+
project_info = @crowdin.project_info(:json)
|
194
|
+
project_info = JSON.parse(project_info)
|
195
|
+
|
196
|
+
# Array of project languages
|
197
|
+
if language == 'all'
|
198
|
+
project_languages = project_info['languages'].collect{ |h| h['code'] }
|
199
|
+
else
|
200
|
+
project_languages = [] << language
|
201
|
+
end
|
202
|
+
|
203
|
+
# Crowdin supported languages list
|
204
|
+
supported_languages = @crowdin.supported_languages(:json)
|
205
|
+
supported_languages = JSON.parse(supported_languages)
|
206
|
+
supported_languages.select!{ |lang| project_languages.include?(lang['crowdin_code']) }
|
207
|
+
|
208
|
+
translated_files = Hash.new{ |hash, key| hash[key] = Array.new }
|
209
|
+
|
210
|
+
# Тут робиться багато припіздатєнької роботи :)
|
211
|
+
# TODO тут тре всьо нахуй переписати!!! НЕНАВИСТЬ
|
212
|
+
@config['files'].each do |file|
|
213
|
+
translation = file['translation']
|
214
|
+
source = file['source'] # relative path to source file in Crowdin project
|
215
|
+
|
216
|
+
sources = []
|
217
|
+
if File.exists?("#{@local_path}#{@sources_root}#{source}")
|
218
|
+
sources << source
|
219
|
+
else
|
220
|
+
Dir.glob("#{@local_path}#{@sources_root}#{source}").each do |f|
|
221
|
+
sources << f.sub("#{@local_path}#{@sources_root}", '')
|
222
|
+
end
|
223
|
+
end
|
224
|
+
|
225
|
+
sources.each do |source|
|
226
|
+
original_path = source.split('/')[1...-1].join('/')
|
227
|
+
original_file_name = source.split('/').last
|
228
|
+
file_extension = original_file_name.split('.').last
|
229
|
+
file_name = original_file_name.split('.').shift
|
230
|
+
|
231
|
+
supported_languages.each do |lang|
|
232
|
+
file = translation.gsub(/%.+?%/, {
|
233
|
+
'%language%' => lang['name'],
|
234
|
+
'%two_letter_code%' => lang['iso_639_1'],
|
235
|
+
'%tree_letter_code%' => lang['iso_639_3'],
|
236
|
+
'%locale%' => lang['locale'],
|
237
|
+
'%locale_with_underscore%' => lang['locale'].gsub('-', '_'),
|
238
|
+
'%original_file_name%' => original_file_name,
|
239
|
+
'%android_code%' => 'values-',
|
240
|
+
'%original_path%' => original_path,
|
241
|
+
'%file_extension%' => file_extension,
|
242
|
+
'%file_name%' => file_extension,
|
243
|
+
})
|
244
|
+
translated_files[lang['crowdin_code']] << { source: @local_path + file, dest: source }
|
245
|
+
end
|
246
|
+
|
247
|
+
end
|
248
|
+
|
249
|
+
end
|
250
|
+
|
251
|
+
params = {}
|
252
|
+
params[:import_duplicates] = options[:import_dublicates] ? 1 : 0
|
253
|
+
params[:import_eq_suggestions] = options[:import_eq_suggestions] ? 1 : 0
|
254
|
+
params[:auto_approve_imported] = options[:auto_approve_imported] ? 1 : 0
|
255
|
+
|
256
|
+
translated_files.each do |language, files|
|
257
|
+
files.each do |file|
|
258
|
+
if File.exist?(file[:source])
|
259
|
+
puts "Uploading #{file[:source]}"
|
260
|
+
@crowdin.upload_translation([] << file, language, params)
|
261
|
+
else
|
262
|
+
puts "Local file #{file[:source]} not exists"
|
263
|
+
end
|
264
|
+
end
|
265
|
+
end
|
266
|
+
|
267
|
+
end # action
|
268
|
+
end # command
|
269
|
+
|
270
|
+
end
|
271
|
+
|
272
|
+
|
273
|
+
desc 'Download existing translations'
|
274
|
+
#arg_name 'Describe arguments to download here'
|
275
|
+
command :download do |c|
|
276
|
+
|
277
|
+
c.desc 'the language of translation you need'
|
278
|
+
c.arg_name 'language_code'
|
279
|
+
c.flag :l, :language, :default_value => 'all'
|
280
|
+
|
281
|
+
c.action do |global_options ,options, args|
|
282
|
+
language = options[:language]
|
283
|
+
|
284
|
+
file = Tempfile.new(language)
|
285
|
+
begin
|
286
|
+
@crowdin.download_translation(language, :output => file)
|
287
|
+
unzip_file(file, @local_path)
|
288
|
+
ensure
|
289
|
+
file.close
|
290
|
+
file.unlink # delete the temp file
|
291
|
+
end
|
292
|
+
end
|
293
|
+
|
294
|
+
end
|
295
|
+
|
296
|
+
pre do |global ,command, options, args|
|
297
|
+
# Pre logic here
|
298
|
+
# Return true to proceed; false to abourt and not call the
|
299
|
+
# chosen command
|
300
|
+
# Use skips_pre before a command to skip this block
|
301
|
+
# on that command only
|
302
|
+
|
303
|
+
@config = YAML.load_file(global[:config])
|
304
|
+
|
305
|
+
#@local_path = @config['local_path'] || Dir.pwd
|
306
|
+
if @config['local_path']
|
307
|
+
if @config['local_path'].start_with?('/')
|
308
|
+
@local_path = @config['local_path']
|
309
|
+
else
|
310
|
+
@local_path = Dir.pwd + '/' + @config['local_path']
|
311
|
+
end
|
312
|
+
else
|
313
|
+
@local_path = Dir.pwd
|
314
|
+
end
|
315
|
+
|
316
|
+
@sources_root = @config['sources_root']
|
317
|
+
|
318
|
+
@crowdin = Crowdin::API.new(api_key: @config['api_key'], project_id: @config['project_id'], base_url: @config['base_url'])
|
319
|
+
|
320
|
+
puts "Executing #{command.name}" if global[:v]
|
321
|
+
true
|
322
|
+
end
|
323
|
+
|
324
|
+
post do |global, command, options, args|
|
325
|
+
# Post logic here
|
326
|
+
# Use skips_post before a command to skip this
|
327
|
+
# block on that command only
|
328
|
+
puts "Executed #{command.name}" if global[:v]
|
329
|
+
end
|
330
|
+
|
331
|
+
on_error do |exception|
|
332
|
+
# Error logic here
|
333
|
+
# return false to skip default error handling
|
334
|
+
true
|
335
|
+
end
|
336
|
+
|
337
|
+
exit run(ARGV)
|
data/crowdin-cli.rdoc
ADDED
data/lib/crowdin-cli.rb
ADDED
metadata
ADDED
@@ -0,0 +1,154 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: crowdin-cli
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.2
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Anton Maminov
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-09-27 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: rake
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
22
|
+
type: :development
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ! '>='
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '0'
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: rdoc
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - ! '>='
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: '0'
|
38
|
+
type: :development
|
39
|
+
prerelease: false
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ! '>='
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: '0'
|
46
|
+
- !ruby/object:Gem::Dependency
|
47
|
+
name: aruba
|
48
|
+
requirement: !ruby/object:Gem::Requirement
|
49
|
+
none: false
|
50
|
+
requirements:
|
51
|
+
- - ! '>='
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '0'
|
54
|
+
type: :development
|
55
|
+
prerelease: false
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ! '>='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
- !ruby/object:Gem::Dependency
|
63
|
+
name: gli
|
64
|
+
requirement: !ruby/object:Gem::Requirement
|
65
|
+
none: false
|
66
|
+
requirements:
|
67
|
+
- - '='
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: 2.1.0
|
70
|
+
type: :runtime
|
71
|
+
prerelease: false
|
72
|
+
version_requirements: !ruby/object:Gem::Requirement
|
73
|
+
none: false
|
74
|
+
requirements:
|
75
|
+
- - '='
|
76
|
+
- !ruby/object:Gem::Version
|
77
|
+
version: 2.1.0
|
78
|
+
- !ruby/object:Gem::Dependency
|
79
|
+
name: rubyzip
|
80
|
+
requirement: !ruby/object:Gem::Requirement
|
81
|
+
none: false
|
82
|
+
requirements:
|
83
|
+
- - '='
|
84
|
+
- !ruby/object:Gem::Version
|
85
|
+
version: 0.9.9
|
86
|
+
type: :runtime
|
87
|
+
prerelease: false
|
88
|
+
version_requirements: !ruby/object:Gem::Requirement
|
89
|
+
none: false
|
90
|
+
requirements:
|
91
|
+
- - '='
|
92
|
+
- !ruby/object:Gem::Version
|
93
|
+
version: 0.9.9
|
94
|
+
- !ruby/object:Gem::Dependency
|
95
|
+
name: crowdin-api
|
96
|
+
requirement: !ruby/object:Gem::Requirement
|
97
|
+
none: false
|
98
|
+
requirements:
|
99
|
+
- - '='
|
100
|
+
- !ruby/object:Gem::Version
|
101
|
+
version: 0.0.2
|
102
|
+
type: :runtime
|
103
|
+
prerelease: false
|
104
|
+
version_requirements: !ruby/object:Gem::Requirement
|
105
|
+
none: false
|
106
|
+
requirements:
|
107
|
+
- - '='
|
108
|
+
- !ruby/object:Gem::Version
|
109
|
+
version: 0.0.2
|
110
|
+
description:
|
111
|
+
email: anton.linux@gmail.com
|
112
|
+
executables:
|
113
|
+
- crowdin-cli
|
114
|
+
extensions: []
|
115
|
+
extra_rdoc_files:
|
116
|
+
- README.rdoc
|
117
|
+
- crowdin-cli.rdoc
|
118
|
+
files:
|
119
|
+
- bin/crowdin-cli
|
120
|
+
- lib/crowdin-cli/version.rb
|
121
|
+
- lib/crowdin-cli.rb
|
122
|
+
- README.rdoc
|
123
|
+
- crowdin-cli.rdoc
|
124
|
+
homepage: https://github.com/mamantoha/crowdin-cli
|
125
|
+
licenses: []
|
126
|
+
post_install_message:
|
127
|
+
rdoc_options:
|
128
|
+
- --title
|
129
|
+
- crowdin-cli
|
130
|
+
- --main
|
131
|
+
- README.rdoc
|
132
|
+
- -ri
|
133
|
+
require_paths:
|
134
|
+
- lib
|
135
|
+
- lib
|
136
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
137
|
+
none: false
|
138
|
+
requirements:
|
139
|
+
- - ! '>='
|
140
|
+
- !ruby/object:Gem::Version
|
141
|
+
version: '0'
|
142
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
143
|
+
none: false
|
144
|
+
requirements:
|
145
|
+
- - ! '>='
|
146
|
+
- !ruby/object:Gem::Version
|
147
|
+
version: '0'
|
148
|
+
requirements: []
|
149
|
+
rubyforge_project:
|
150
|
+
rubygems_version: 1.8.24
|
151
|
+
signing_key:
|
152
|
+
specification_version: 3
|
153
|
+
summary: A description of your project
|
154
|
+
test_files: []
|