xlocalize 0.1.2 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: ab478e509c23afe6342a33614fa8178181b96bd4
4
- data.tar.gz: eb06d69ff88c064ac6d7edd571f4f2c233235845
3
+ metadata.gz: 792155074986084e3e7e83a6eb58336bb00c5534
4
+ data.tar.gz: 7a2d092e9300a12d26c88493813ea5dee23268c3
5
5
  SHA512:
6
- metadata.gz: 358137228061035959ff6a38fb12bffa575355d59f6a936d30449feeb0296d12911223271791c90275d2c4bb19df559492d3cbea737411ce89457d1b98f2a229
7
- data.tar.gz: 0d681b7ddd8da25f7a27db4038abd733f4e18b931e44c8b63bef826e017369a29630dd25a71628b966cec7e5fc480b1c8198ff7925bb97e79c40fddfeb22ca31
6
+ metadata.gz: 736409b41ba7b868a91c953c8d12e1aa663c9b973a16ba13e165911659533df17d277f0556687ce13d8acd78f7152429a3bee59e309b822ddbe70e1cb9601441
7
+ data.tar.gz: 34b303e10d3990a19101b1da6d4fa8561bf00a7f64b62fa4f4f1a45c6136e1539fb2ca468c00b938d36d4260645a200b7d24af56d46371d1d6babf18a03d0910
data/.gitignore CHANGED
@@ -8,3 +8,17 @@
8
8
  /pkg/
9
9
  /spec/reports/
10
10
  /tmp/
11
+
12
+ # iOS specific ignores, used in sample projects inside the repo
13
+ *.pbxuser
14
+ !default.pbxuser
15
+ *.mode1v3
16
+ !default.mode1v3
17
+ *.mode2v3
18
+ !default.mode2v3
19
+ *.perspectivev3
20
+ !default.perspectivev3
21
+ xcuserdata/
22
+ *.moved-aside
23
+ *.xccheckout
24
+ *.xcscmblueprint
@@ -1,10 +1,17 @@
1
1
  require 'xlocalize/webtranslateit'
2
2
  require 'colorize'
3
3
  require 'nokogiri'
4
+ require 'plist'
5
+ require 'yaml'
6
+ require 'pathname'
4
7
 
5
8
  module Xlocalize
6
9
  class Executor
7
10
 
11
+ def plurals_file_name(locale)
12
+ return locale_file_name(locale) << '_plurals.yml'
13
+ end
14
+
8
15
  def locale_file_name(locale)
9
16
  return "#{locale}.xliff"
10
17
  end
@@ -12,22 +19,34 @@ module Xlocalize
12
19
  def export_master(wti, project, target, excl_prefix, master_lang)
13
20
  master_file_name = locale_file_name(master_lang)
14
21
 
15
- system "xcodebuild -exportLocalizations -localizationPath ./ -project #{project}"
16
- purelyze(master_file_name, target, excl_prefix)
22
+ # hacky way to finish xcodebuild -exportLocalizations script, because
23
+ # since Xcode7.3 & OS X Sierra script hangs even though it produces
24
+ # xliff output
25
+ # http://www.openradar.me/25857436
26
+ File.delete(master_file_name) if File.exist?(master_file_name)
27
+ system "xcodebuild -exportLocalizations -localizationPath ./ -project #{project} & sleep 0"
28
+ while !File.exist?(master_file_name) do
29
+ sleep(1)
30
+ end
31
+
32
+ purelyze(master_lang, target, excl_prefix, project)
17
33
 
18
34
  # Pushing master file to WebtranslateIt
19
35
  begin
20
36
  puts "Uploading master file to WebtranslateIt"
21
- File.open(master_file_name, "r") {|file|
22
- wti.push_master(file)
23
- puts "Done.".green
24
- }
37
+ file = File.open(master_file_name, 'r')
38
+ plurals_file = File.open(plurals_file_name(master_lang), 'r')
39
+ wti.push_master(file, plurals_file)
40
+ puts "Done.".green
25
41
  rescue => err
26
42
  puts err.to_s.red
43
+ ensure
44
+ file.close unless file.nil?
27
45
  end if !wti.nil?
28
46
  end
29
47
 
30
- def purelyze(locale_file_name, target, excl_prefix)
48
+ def purelyze(locale, target, excl_prefix, project)
49
+ locale_file_name = locale_file_name(locale)
31
50
  target_prefix = "#{target}/"
32
51
  doc = Nokogiri::XML(open(locale_file_name))
33
52
 
@@ -42,62 +61,120 @@ module Xlocalize
42
61
  node.parent.remove if node.content.start_with?(excl_prefix)
43
62
  }
44
63
 
64
+ puts "Filtering plurals"
65
+ plurals = {}
66
+ doc.xpath("//xmlns:file").each { |node|
67
+ fname = node["original"]
68
+ next if !fname.end_with?(".strings")
69
+ fname_stringsdict = fname << 'dict'
70
+ file_full_path = Pathname.new(project).split.first.to_s << '/' << fname_stringsdict
71
+ next if !File.exist?(file_full_path)
72
+
73
+ Plist::parse_xml(file_full_path).each do |key, val|
74
+ values = val["value"]
75
+ transl = values.select { |k, v| ['zero', 'one', 'few', 'other'].include?(k) }
76
+ plurals[fname_stringsdict] = {key => transl}
77
+ sel = 'body > trans-unit[id="' << key << '"]'
78
+ node.css(sel).remove
79
+ end
80
+ }
81
+
45
82
  puts "Removing all files having no trans-unit elements after removal"
46
83
  doc.xpath("//xmlns:body").each { |node|
47
84
  node.parent.remove if node.elements.count == 0
48
85
  }
49
86
 
50
87
  puts "Writing modified XLIFF file to #{locale_file_name}"
51
- File.open(locale_file_name, "w") {|file| file.write(doc.to_xml) }
88
+ File.open(locale_file_name, 'w') { |f| f.write(doc.to_xml) }
89
+
90
+ if !plurals.empty?
91
+ puts "Writing plurals to plurals YAML file"
92
+ File.open(plurals_file_name(locale), 'w') { |f| f.write({locale => plurals}.to_yaml) }
93
+ end
52
94
  end
53
95
 
54
96
  def download(wti, locales)
55
97
  begin
56
98
  locales.each do |locale|
57
- puts "Downloading localized file for #{locale} translation"
99
+ puts "Downloading translations for #{locale}"
100
+ translations = wti.pull(locale)
101
+
58
102
  File.open("#{locale}.xliff", "w") {|file|
59
- wti.pull(file, locale)
60
- puts "Done.".green
103
+ file.write(translations['xliff'])
104
+ puts "Done saving xliff.".green
61
105
  }
106
+
107
+ if !translations['plurals'].nil?
108
+ File.open("#{locale}_plurals.yaml", "w") {|file|
109
+ file.write(translations['plurals'])
110
+ puts "Done saving plurals.".green
111
+ }
112
+ end
62
113
  end
63
114
  rescue => err
64
115
  puts err.to_s.red
65
116
  end
66
117
  end
67
118
 
68
- def import(locales)
69
- locales.each do |locale|
70
- doc = Nokogiri::XML(open("#{locale}.xliff"))
71
-
72
- doc.xpath("//xmlns:file").each { |node|
73
- file_name = node["original"]
74
- parts = file_name.split('/')
75
- name = ""
76
- parts.each_with_index {|part, idx|
77
- name += "/" if idx > 0
78
- if part.end_with?(".lproj")
79
- name += "#{locale}.lproj"
80
- elsif idx+1 == parts.count
81
- # TODO: join all parts till the last '.'
82
- name += "#{part.split('.')[0]}.strings"
83
- else
84
- name += part
85
- end
86
- }
87
-
88
- File.open(name, "w") {|file|
89
- (node > "body > trans-unit").each {|trans_unit|
90
- key = trans_unit["id"]
91
- target = (trans_unit > "target").text
92
- note = (trans_unit > "note").text
93
- note = "(No Commment)" if note.length <= 0
94
-
95
- file.write "/* #{note} */\n"
96
- file.write "\"#{key}\" = #{target.inspect};\n\n"
97
- }
119
+ def import_xliff(locale)
120
+ doc = Nokogiri::XML(open("#{locale}.xliff"))
121
+
122
+ doc.xpath("//xmlns:file").each do |node|
123
+ file_name = node["original"]
124
+ parts = file_name.split('/')
125
+ name = ""
126
+ parts.each_with_index do |part, idx|
127
+ name += "/" if idx > 0
128
+ if part.end_with?(".lproj")
129
+ name += "#{locale}.lproj"
130
+ elsif idx+1 == parts.count
131
+ # TODO: join all parts till the last '.'
132
+ name += "#{part.split('.')[0]}.strings"
133
+ else
134
+ name += part
135
+ end
136
+ end
137
+
138
+ File.open(name, "w") do |file|
139
+ (node > "body > trans-unit").each do |trans_unit|
140
+ key = trans_unit["id"]
141
+ target = (trans_unit > "target").text
142
+ note = (trans_unit > "note").text
143
+ note = "(No Commment)" if note.length <= 0
144
+
145
+ file.write "/* #{note} */\n"
146
+ file.write "\"#{key}\" = #{target.inspect};\n\n"
147
+ end
148
+ end
149
+
150
+ end
151
+ end
152
+
153
+ def import_plurals_if_needed(locale)
154
+ plurals_fname = "#{locale}_plurals.yaml"
155
+ return if !File.exist?(plurals_fname)
156
+ plurals_yml = YAML.load_file(plurals_fname)
157
+ plurals_yml[locale].each do |fname, trans_units|
158
+ content = {}
159
+ trans_units.each do |key, vals|
160
+ content[key] = {
161
+ "NSStringLocalizedFormatKey" => "%\#@value@",
162
+ "value" => vals.merge({
163
+ "NSStringFormatSpecTypeKey" => "NSStringPluralRuleType",
164
+ "NSStringFormatValueTypeKey" => "d"
165
+ })
98
166
  }
167
+ end
168
+ File.open(fname, 'w') { |f| f.write content.to_plist }
169
+ end
170
+ end
99
171
 
100
- }
172
+ def import(locales)
173
+ puts 'Importing translations'
174
+ locales.each do |locale|
175
+ import_xliff(locale)
176
+ import_plurals_if_needed(locale)
177
+ puts "Done #{locale}".green
101
178
  end
102
179
  end
103
180
  end
@@ -1,4 +1,4 @@
1
1
  module Xlocalize
2
- VERSION = "0.1.2"
2
+ VERSION = "0.2.0"
3
3
  DESCRIPTION = "Xcode localizations import/export helper tool"
4
4
  end
@@ -6,7 +6,10 @@ module Xlocalize
6
6
  class WebtranslateIt
7
7
 
8
8
  attr_reader :http
9
- attr_reader :key, :source_locale, :master_file_id
9
+ attr_reader :key, :source_locale
10
+
11
+ attr_reader :xliff_file_id
12
+ attr_reader :plurals_file_id
10
13
 
11
14
  def initialize(key)
12
15
  @key = key
@@ -18,17 +21,18 @@ module Xlocalize
18
21
  project = JSON.parse(response.body)["project"]
19
22
  @source_locale = project["source_locale"]["code"]
20
23
  project["project_files"].each {|file|
21
- if file["locale_code"] == @source_locale
22
- @master_file_id = file["id"]
23
- break
24
- end
24
+ next if file["locale_code"] != @source_locale
25
+ @xliff_file_id = file["id"] if file['name'].end_with? '.xliff'
26
+ @plurals_file_id = file["id"] if file['name'] == 'plurals.yaml'
25
27
  }
26
- raise "Could not find master file for source locale #{@source_locale}" if @master_file_id.nil?
28
+ raise "Could not find master xliff file for source locale #{@source_locale}" if @xliff_file_id.nil?
27
29
  }
28
30
  end
29
31
 
30
- def push_master(file, override = true)
31
- request = Net::HTTP::Put::Multipart.new("/api/projects/#{@key}/files/#{@master_file_id}/locales/#{@source_locale}", {
32
+ def push_master(file, plurals_file, override = true)
33
+ puts 'Updating xliff file'
34
+ # uploding master xliff file
35
+ request = Net::HTTP::Put::Multipart.new("/api/projects/#{@key}/files/#{@xliff_file_id}/locales/#{@source_locale}", {
32
36
  "file" => UploadIO.new(file, "text/plain", file.path),
33
37
  "merge" => !override,
34
38
  "ignore_missing" => true,
@@ -40,12 +44,51 @@ module Xlocalize
40
44
  raise JSON.parse(res.body)["error"]
41
45
  end
42
46
  }
47
+
48
+ if @plurals_file_id.nil?
49
+ puts 'Creating plurals file'
50
+ # /api/projects/:project_token/files [POST]
51
+ request = Net::HTTP::Post::Multipart.new("/api/projects/#{@key}/files", {
52
+ "file" => UploadIO.new(plurals_file, "text/plain", plurals_file.path),
53
+ "name" => "plurals.yaml",
54
+ "low_priority" => false
55
+ })
56
+ @http.request(request) { |res|
57
+ if !res.code.to_i.between?(200, 300)
58
+ raise JSON.parse(res.body)["error"]
59
+ end
60
+ }
61
+ else
62
+ puts 'Updating plurals file'
63
+ # /api/projects/:project_token/files/:master_project_file_id/locales/:locale_code [PUT]
64
+ request = Net::HTTP::Put::Multipart.new("/api/projects/#{@key}/files/#{@plurals_file_id}/locales/#{@source_locale}", {
65
+ "file" => UploadIO.new(plurals_file, "text/plain", plurals_file.path),
66
+ "merge" => !override,
67
+ "ignore_missing" => true,
68
+ "label" => "",
69
+ "low_priority" => false
70
+ })
71
+ @http.request(request) { |res|
72
+ if !res.code.to_i.between?(200, 300)
73
+ raise JSON.parse(res.body)["error"]
74
+ end
75
+ }
76
+ end
43
77
  end
44
78
 
45
- def pull(file, locale)
46
- http.request(Net::HTTP::Get.new("/api/projects/#{@key}/files/#{master_file_id}/locales/#{locale}")) {|response|
47
- file.write(response.body)
48
- }
79
+ def pull(locale)
80
+ # downloading master xliff file
81
+ data = {}
82
+ res = http.request(Net::HTTP::Get.new("/api/projects/#{@key}/files/#{@xliff_file_id}/locales/#{locale}"))
83
+ raise JSON.parse(res.body)["error"] if !res.code.to_i.between?(200, 300)
84
+ data['xliff'] = res.body
85
+ # downloading master plurals file
86
+ if !@plurals_file_id.nil?
87
+ res = http.request(Net::HTTP::Get.new("/api/projects/#{@key}/files/#{@plurals_file_id}/locales/#{locale}"))
88
+ raise JSON.parse(res.body)["error"] if !res.code.to_i.between?(200, 300)
89
+ data['plurals'] = res.body
90
+ end
91
+ return data
49
92
  end
50
93
 
51
94
  end
data/xlocalize.gemspec CHANGED
@@ -19,10 +19,13 @@ Gem::Specification.new do |spec|
19
19
  spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
20
20
  spec.require_paths = ["lib"]
21
21
 
22
- spec.add_runtime_dependency 'nokogiri'
23
- spec.add_runtime_dependency 'commander'
24
- spec.add_runtime_dependency 'colorize', '~> 0'
25
-
26
- spec.add_development_dependency "bundler", "~> 1.10"
27
- spec.add_development_dependency "rake", "~> 10.0"
22
+ spec.add_runtime_dependency 'nokogiri', '~> 1.6'
23
+ spec.add_runtime_dependency 'commander', '~> 4.4'
24
+ spec.add_runtime_dependency 'colorize', '~> 0.8'
25
+ spec.add_runtime_dependency 'plist', '~> 3.2'
26
+ spec.add_runtime_dependency 'multipart-post', '~> 2.0'
27
+
28
+ spec.add_development_dependency 'bundler', '~> 1.10'
29
+ spec.add_development_dependency 'rake', '~> 10.0'
30
+ spec.add_development_dependency 'rspec', '~> 3.5'
28
31
  end
metadata CHANGED
@@ -1,57 +1,85 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: xlocalize
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.2
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Viktoras Laukevičius
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2016-07-13 00:00:00.000000000 Z
11
+ date: 2017-02-14 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: nokogiri
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
- - - ">="
17
+ - - "~>"
18
18
  - !ruby/object:Gem::Version
19
- version: '0'
19
+ version: '1.6'
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
- - - ">="
24
+ - - "~>"
25
25
  - !ruby/object:Gem::Version
26
- version: '0'
26
+ version: '1.6'
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: commander
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
- - - ">="
31
+ - - "~>"
32
32
  - !ruby/object:Gem::Version
33
- version: '0'
33
+ version: '4.4'
34
34
  type: :runtime
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
- - - ">="
38
+ - - "~>"
39
39
  - !ruby/object:Gem::Version
40
- version: '0'
40
+ version: '4.4'
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: colorize
43
43
  requirement: !ruby/object:Gem::Requirement
44
44
  requirements:
45
45
  - - "~>"
46
46
  - !ruby/object:Gem::Version
47
- version: '0'
47
+ version: '0.8'
48
48
  type: :runtime
49
49
  prerelease: false
50
50
  version_requirements: !ruby/object:Gem::Requirement
51
51
  requirements:
52
52
  - - "~>"
53
53
  - !ruby/object:Gem::Version
54
- version: '0'
54
+ version: '0.8'
55
+ - !ruby/object:Gem::Dependency
56
+ name: plist
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '3.2'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '3.2'
69
+ - !ruby/object:Gem::Dependency
70
+ name: multipart-post
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '2.0'
76
+ type: :runtime
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '2.0'
55
83
  - !ruby/object:Gem::Dependency
56
84
  name: bundler
57
85
  requirement: !ruby/object:Gem::Requirement
@@ -80,6 +108,20 @@ dependencies:
80
108
  - - "~>"
81
109
  - !ruby/object:Gem::Version
82
110
  version: '10.0'
111
+ - !ruby/object:Gem::Dependency
112
+ name: rspec
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - "~>"
116
+ - !ruby/object:Gem::Version
117
+ version: '3.5'
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - "~>"
123
+ - !ruby/object:Gem::Version
124
+ version: '3.5'
83
125
  description:
84
126
  email:
85
127
  - viktoras.laukevicius@yahoo.com