xlocalize 0.1.2 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml 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