babelyoda 1.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.
Files changed (7) hide show
  1. data/CHANGELOG +2 -0
  2. data/LICENSE +20 -0
  3. data/README.rdoc +17 -0
  4. data/VERSION +1 -0
  5. data/bin/babelyoda +404 -0
  6. data/lib/babelyoda.rb +0 -0
  7. metadata +92 -0
data/CHANGELOG ADDED
@@ -0,0 +1,2 @@
1
+ v1.1.0. Initial release.
2
+
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2009 Andrey Subbotin
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.rdoc ADDED
@@ -0,0 +1,17 @@
1
+ = babelyoda
2
+
3
+ A simple utility to push/pull l10n resources of an iPhone project to/from the translators.
4
+
5
+ == Note on Patches/Pull Requests
6
+
7
+ * Fork the project.
8
+ * Make your feature addition or bug fix.
9
+ * Add tests for it. This is important so I don't break it in a
10
+ future version unintentionally.
11
+ * Commit, do not mess with rakefile, version, or history.
12
+ (if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull)
13
+ * Send me a pull request. Bonus points for topic branches.
14
+
15
+ == Copyright
16
+
17
+ Copyright (c) 2010 Andrey Subbotin. See LICENSE for details.
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 1.2.0
data/bin/babelyoda ADDED
@@ -0,0 +1,404 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'rubygems'
4
+ require 'fileutils'
5
+ require 'plist'
6
+ require 'term/ansicolor'
7
+ require 'tmpdir'
8
+ require 'yaml'
9
+
10
+ include Term::ANSIColor
11
+
12
+ # Main block
13
+
14
+ def main
15
+ banner
16
+ check_args
17
+ setup
18
+ print_config
19
+ prepare_folders
20
+ init_lproj_dirs
21
+ apply_incremental_translations
22
+ gen_src_code_strings
23
+ extract_untranslated_src_code_strings
24
+ gen_src_xib_strings
25
+ extract_untranslated_src_xib_strings
26
+ localize_xibs_incrementally
27
+ pack_tmp_lproj
28
+ push_tmp_lproj
29
+ FileUtils.rm_rf $config['tmp_dir'], :verbose => true
30
+ success "All done!"
31
+ end
32
+
33
+ # Aux methods
34
+
35
+ def banner
36
+ puts "Babel Yoda v1.2.0"
37
+ puts "© 2010 Andrey Subbotin <andrey@subbotin.me>"
38
+ end
39
+
40
+ def usage
41
+ puts ""
42
+ puts "Usage: babelyoda [<rules.babelyoda>]"
43
+ puts ""
44
+ puts "<rules.babelyoda> = babel yoda config file, default is rules.babelyoda"
45
+ end
46
+
47
+ def check_args
48
+ if ARGV.length > 1
49
+ usage
50
+ exit 1
51
+ end
52
+ end
53
+
54
+ def setup
55
+ $config = Hash.new
56
+ global_config_filename = File.expand_path('~/.babelyoda')
57
+ $config.merge!(YAML.load_file(global_config_filename)) if File.exist?(global_config_filename)
58
+ config = YAML.load_file(ARGV[0].nil? ? 'rules.babelyoda' : ARGV[0])
59
+ config['tmp_dir'] = File.join Dir.tmpdir, "epl.babelyoda.#{$$}"
60
+ config['src_dir'] = File.join config['tmp_dir'], 'src'
61
+ config['startwd'] = Dir.getwd
62
+ config['src_lang'] = Plist::parse_xml(config['plist'])['CFBundleDevelopmentRegion']
63
+ config['src_lang_lproj'] = config['src_lang'] + '.lproj'
64
+ config['src_lang_lproj_dir'] = File.join(config['strings_folder'], config['src_lang_lproj'])
65
+ config['src_lang_code_strings_file'] = File.join(config['src_lang_lproj_dir'], 'Localizable.strings')
66
+ config['initial_strings_file'] = File.join(config['strings_folder'], 'Localizable.strings')
67
+ $config.merge!(config)
68
+ $config['products_folder'] = File.expand_path($config['products_folder'])
69
+ $config['time_badge'] = Time.now.utc.strftime('%Y%m%d_%H%M')
70
+ end
71
+
72
+ def print_config ; status "CONFIG" ; y $config ; end
73
+ def exe(cmd) ; putcmd cmd ; system cmd ; end
74
+ def putcmd(cmd) ; print magenta, "CMD: #{cmd}", reset, "\n" ; end
75
+ def status(msg) ; print blue, "--- #{msg} ---", reset, "\n" ; end
76
+ def success(msg) ; print green, bold, 'SUCCESS: ', msg, reset, "\n" ; end
77
+ def error(msg) ; print red, bold, 'ERROR: ', msg, reset, "\n" ; exit 1 ; end
78
+ def escape_cmd_args(args) ; args.collect{ |arg| "'#{arg}'"}.join(' ') ; end
79
+
80
+ # Globbers
81
+
82
+ def all_languages
83
+ result = Array.new
84
+ result << $config['languages']
85
+ result << $config['src_lang']
86
+ result.flatten!.sort!
87
+ end
88
+
89
+ def all_lproj_dirs
90
+ result = Array.new
91
+ result << dst_lproj_dirs
92
+ result << src_lproj_dir
93
+ result.flatten!.sort!
94
+ end
95
+
96
+ def src_lproj_dir
97
+ $config['src_lang_lproj_dir']
98
+ end
99
+
100
+ def dst_lproj_dirs
101
+ $config['languages'].collect{ |l|
102
+ lproj_dir_for_lang(l)
103
+ }.sort!
104
+ end
105
+
106
+ def lproj_dir_for_lang(lang)
107
+ File.join($config['strings_folder'], "#{lang}.lproj")
108
+ end
109
+
110
+ def tmp_lproj_dir_for_lang(lang)
111
+ File.join($config['tmp_dir'], "#{lang}.lproj")
112
+ end
113
+
114
+ def src_code_files
115
+ $config['folders'].collect{ |dir|
116
+ Dir.glob(File.join(dir, '**', '*.{c,h,m,hh,mm}'))
117
+ }.flatten!.sort!
118
+ end
119
+
120
+ def dst_code_strings_files
121
+ $config['languages'].collect{ |l|
122
+ File.join(lproj_dir_for_lang(l), 'Localizable.strings')
123
+ }.sort!
124
+ end
125
+
126
+ def all_code_strings_files
127
+ result = Array.new
128
+ result << dst_code_strings_files
129
+ result << $config['src_lang_code_strings_file']
130
+ result.flatten!.sort!
131
+ end
132
+
133
+ def xib_files_for_lang(lang)
134
+ $config['folders'].collect{ |dir|
135
+ Dir.glob(File.join(dir, '**', lang + '.lproj', '*.xib'))
136
+ }.flatten!.sort!
137
+ end
138
+
139
+ def dst_xib_files
140
+ $config['languages'].collect{ |lang|
141
+ xib_files_for_lang(lang)
142
+ }.flatten!.sort!
143
+ end
144
+
145
+ def src_xib_files
146
+ xib_files_for_lang($config['src_lang_lproj'])
147
+ end
148
+
149
+ def strings_files_for_lang(lang)
150
+ $config['folders'].collect{ |dir|
151
+ Dir.glob(File.join(dir, '**', lang + '.lproj', '*.strings'))
152
+ }.flatten!.sort!
153
+ end
154
+
155
+ def all_strings_files
156
+ all_languages.collect{ |lang| strings_files_for_lang(lang) }.flatten!.sort!
157
+ end
158
+
159
+ def strings_file_for_xib_lang(xib, lang)
160
+ parts = File.split(xib)
161
+ lang_split = File.split(parts[-2])
162
+ lang_split[-1] = lang + '.lproj'
163
+ parts[-2] = File.join(lang_split)
164
+ parts[-1] = parts[-1].slice(0, parts[-1].length - 4) + '.strings'
165
+ result = File.join(parts)
166
+ result
167
+ end
168
+
169
+ def xib_file_for_xib_lang(xib, lang)
170
+ xib_filename = File.split(xib).last
171
+ parts = File.split(xib)
172
+ lang_split = File.split(parts[-2])
173
+ lang_split[-1] = lang + '.lproj'
174
+ parts[-2] = File.join(lang_split)
175
+ File.join(parts)
176
+ end
177
+
178
+ def strings_files_for_xib(xib)
179
+ all_languages.each.collect { |lang| strings_file_for_xib_lang(xib, lang) }
180
+ end
181
+
182
+ # Globs all incremental translations for the given .strings file and language.
183
+ def incremental_translations(file, lang)
184
+ filename = File.split(file).last
185
+ mask = "#{$config['prefix']}_*_*_#{lang}_#{filename}"
186
+ Dir.glob(File.join($config['incremental_translations_folder'], mask)).sort!
187
+ end
188
+
189
+ # Processing
190
+
191
+ def prepare_folders
192
+ status "PREPARING FOLDERS"
193
+ FileUtils.mkdir_p $config['incremental_translations_folder'], :verbose => true
194
+ FileUtils.mkdir_p $config['old_xibs_folder'], :verbose => true
195
+ FileUtils.mkdir_p File.join($config['tmp_dir'], 'zips'), :verbose => true
196
+ make_tmp_lproj_dirs
197
+ end
198
+
199
+ def make_tmp_lproj_dirs
200
+ FileUtils.mkdir_p tmp_lproj_dir_for_lang($config['src_lang']), :verbose => true
201
+ FileUtils.mkdir_p $config['languages'].sort.collect{ |lang| tmp_lproj_dir_for_lang(lang) }, :verbose => true
202
+ end
203
+
204
+ def init_lproj_dirs
205
+ status "PREPARING LPROJ FOLDERS"
206
+ if File.exists?($config['initial_strings_file']) && File.exists?($config['src_lang_code_strings_file'])
207
+ error "Both '#{$config['initial_strings_file']}' and '#{$config['src_lang_code_strings_file']}' exist!"
208
+ end
209
+ FileUtils.mkdir_p all_lproj_dirs, :verbose => true
210
+ end
211
+
212
+ # Iterate over all the .strings files and apply
213
+ # all the translations found for each in the
214
+ # incremental translations folder.
215
+ def apply_incremental_translations
216
+ all_languages.each do |lang|
217
+ strings_files_for_lang(lang).each do |strings_file|
218
+ status "APPLYING INCREMENTAL TRANSLATIONS FOR '#{strings_file}'"
219
+ incremental_translations(strings_file, lang).each do |translation|
220
+ combine_strings(translation, strings_file)
221
+ end
222
+ end
223
+ end
224
+ end
225
+
226
+ # Generate .strings file into the temporary directory,
227
+ # touch the target file so it's created if it didn't exist,
228
+ # and then merge it with the new file, preserving any translations made.
229
+ def gen_src_code_strings
230
+ status "GENERATING STRINGS FILE FOR THE SOURCE LANGUAGE"
231
+ genstrings src_code_files, $config['tmp_dir']
232
+ tmp_strings_file = File.join($config['tmp_dir'], 'Localizable.strings')
233
+ merge_strings tmp_strings_file, all_code_strings_files
234
+ end
235
+
236
+ def genstrings(files, output_path)
237
+ files_safe = escape_cmd_args(files)
238
+ rc = exe "genstrings -o '#{output_path}' #{files_safe}"
239
+ error "Failed to generate strings" unless rc
240
+ end
241
+
242
+ def gen_src_xib_strings
243
+ status "GENERATING STRINGS FILES FOR THE SOURCE LANGUAGE XIBS"
244
+ src_xib_files.each do |xib|
245
+ tmp_strings_file = gen_xib_strings xib
246
+ merge_strings tmp_strings_file, strings_files_for_xib(xib)
247
+ end
248
+ end
249
+
250
+ def tmp_filename_for_xib_strings(xib)
251
+ xib_filename = File.split(xib).last
252
+ strings_filename = xib_filename.slice(0, xib.length - 4) + '.strings'
253
+ File.join($config['tmp_dir'], strings_filename)
254
+ end
255
+
256
+ def gen_xib_strings(xib)
257
+ strings = tmp_filename_for_xib_strings(xib)
258
+ rc = exe "ibtool --generate-strings-file '#{strings}' '#{xib}'"
259
+ error "Failed to generate strings for file '#{xib}'" unless rc
260
+ strings
261
+ end
262
+
263
+ def merge_strings(src_strings, dst_strings)
264
+ targets = dst_strings.is_a?(String) ? [dst_strings] : dst_strings
265
+ puts "DST_STRINGS: #{dst_strings}"
266
+ targets.each do |target|
267
+ status "MERGING STRINGS AT '#{src_strings}' INTO '#{target}'"
268
+ FileUtils.touch target
269
+ rc = exe "wincent-strings-util --base '#{src_strings}' --merge '#{target}' --output '#{target}'"
270
+ error "Failed to merge '#{src_strings}' into '#{target}'" unless rc
271
+ end
272
+ end
273
+
274
+ def extract_untranslated_src_code_strings
275
+ tmp_strings_file = File.join($config['tmp_dir'], 'Localizable.strings')
276
+ $config['languages'].sort.each do |lang|
277
+ src_file = File.join(lproj_dir_for_lang(lang), 'Localizable.strings')
278
+ dst_file = File.join(tmp_lproj_dir_for_lang(lang), mangled_name('Localizable.strings', lang))
279
+ status "EXTRACTING UNTRANSLATED STRINGS FROM '#{src_file}' TO '#{dst_file}'"
280
+ rc = exe "wincent-strings-util --base '#{tmp_strings_file}' --extract '#{src_file}' --output '#{dst_file}'"
281
+ error "Failed to extract untranslated string from '#{src_file}' into '#{dst_file}' based on '#{tmp_strings_file}'" unless rc
282
+ end
283
+ end
284
+
285
+ def extract_untranslated_src_xib_strings
286
+ src_xib_files.each do |xib|
287
+ tmp_strings_file = tmp_filename_for_xib_strings(xib)
288
+ $config['languages'].sort.each do |lang|
289
+ src_file = strings_file_for_xib_lang(xib, lang)
290
+ src_filename = File.split(src_file).last
291
+ dst_file = File.join(tmp_lproj_dir_for_lang(lang), mangled_name(src_filename, lang))
292
+
293
+ status "EXTRACTING UNTRANSLATED STRINGS FROM '#{src_file}' TO '#{dst_file}'"
294
+ rc = exe "wincent-strings-util --base '#{tmp_strings_file}' --extract '#{src_file}' --output '#{dst_file}'"
295
+ error "Failed to extract untranslated string from '#{src_file}' into '#{dst_file}' based on '#{tmp_strings_file}'" unless rc
296
+ end
297
+ end
298
+ end
299
+
300
+ def combine_strings(incremental_strings, old_strings)
301
+ status "COMBINING STRINGS FROM '#{incremental_strings}' INTO '#{old_strings}'"
302
+ FileUtils.touch old_strings
303
+ rc = exe "wincent-strings-util --base '#{old_strings}' --combine '#{incremental_strings}' --output '#{old_strings}'"
304
+ error "Failed to combine '#{incremental_strings}' into '#{old_strings}'" unless rc
305
+ end
306
+
307
+ def pack_tmp_lproj
308
+ # Copy the source language Localizable.strings into the temporary lproj folder.
309
+ status "COPYING STRINGS FILE FOR '#{$config['src_lang']}'"
310
+ FileUtils.cp $config['src_lang_code_strings_file'],
311
+ File.join(tmp_lproj_dir_for_lang($config['src_lang']), mangled_name('Localizable.strings', $config['src_lang'])),
312
+ :verbose => true
313
+ src_xib_files.each do |xib|
314
+ strings = strings_file_for_xib_lang(xib, $config['src_lang'])
315
+ strings_filename = File.split(strings).last
316
+ FileUtils.cp strings,
317
+ File.join(tmp_lproj_dir_for_lang($config['src_lang']), mangled_name(strings_filename, $config['src_lang'])),
318
+ :verbose => true
319
+ end
320
+
321
+ # Pack all the temporary lproj folders.
322
+ all_languages.sort.each do |lang|
323
+ status "PACKING RESOURCES FOR '#{lang}'"
324
+ FileUtils.cd(tmp_lproj_dir_for_lang(lang), :verbose => true) do |dir|
325
+ rc = exe "zip -r -9 -y '../zips/#{zipname_for_lang(lang)}' *"
326
+ error "Failed to pack resources for '#{lang}'" unless rc
327
+ end
328
+ end
329
+ end
330
+
331
+ def push_tmp_lproj
332
+ # Move all zips from the temporary lproj folder into the products folder (e.g. ~/Desktop).
333
+ FileUtils.mv Dir.glob(File.join($config['tmp_dir'], 'zips', '*.zip')), $config['products_folder'], :verbose => true
334
+ end
335
+
336
+ # Generates a mangled filename for the given file and language.
337
+ def mangled_name(file, lang)
338
+ "#{$config['prefix']}_#{$config['time_badge']}_#{lang}_#{file}"
339
+ end
340
+
341
+ # Genarates ZIP file name for the given language
342
+ def zipname_for_lang(lang)
343
+ "#{$config['prefix']}_#{$config['time_badge']}_#{lang}_Localization.zip"
344
+ end
345
+
346
+ def localize_xibs_incrementally
347
+ # ibtool
348
+ # --previous-file path_to_project/English.lproj/MainWindow.old.xib # old_dst_xib
349
+ # --incremental-file path_to_project/fr.lproj/MainWindow.old.xib # old_src_xib
350
+ # --strings-file path_to_strings/fr/MainWindow.strings # dst_strings
351
+ # --localize-incremental
352
+ # --write path_to_project/fr.lproj/MainWindow.xib # dst_xib
353
+ # path_to_project/English.lproj/MainWindow.new.xib # src_xib
354
+
355
+ $config['languages'].sort.each do |lang|
356
+ puts "XIB-LANG: #{lang}"
357
+ xib_files_for_lang(lang).each do |dst_xib|
358
+ puts "XIB-DST: #{dst_xib}"
359
+ dst_strings = dst_xib.slice(0, dst_xib.length - 4) + '.strings'
360
+ src_xib = xib_file_for_xib_lang(dst_xib, $config['src_lang'])
361
+ old_src_xib = old_xib_for_xib_lang(src_xib, $config['src_lang'])
362
+ FileUtils.cp src_xib, old_src_xib, :preserve => true, :verbose => true
363
+ FileUtils.cp src_xib, dst_xib, :preserve => true, :verbose => true
364
+ old_dst_xib = old_xib_for_xib_lang(dst_xib, lang)
365
+ FileUtils.cp dst_xib, old_dst_xib, :preserve => true, :verbose => true
366
+
367
+ ncmd = ['ibtool', '--previous-file', "'#{old_dst_xib}'",
368
+ '--incremental-file', "'#{old_src_xib}'",
369
+ '--strings-file', "'#{dst_strings}'",
370
+ '--localize-incremental',
371
+ '--write', "#{dst_xib}",
372
+ "#{src_xib}"].join(' ')
373
+ puts "XIB-CMD: #{ncmd}"
374
+ rc = exe ncmd
375
+ error "Failed to localize a XIB incrementally" unless rc
376
+ end
377
+ end
378
+ end
379
+
380
+ def xib_file_for_xib_lang(xib, lang)
381
+ xib_filename = File.split(xib).last
382
+ parts = File.split(xib)
383
+ lang_split = File.split(parts[-2])
384
+ lang_split[-1] = lang + '.lproj'
385
+ parts[-2] = File.join(lang_split)
386
+ result = File.join(parts)
387
+ unless lang == $config['src_lang']
388
+ FileUtils.cp xib_file_for_xib_lang(xib, $config['src_lang']),
389
+ result, :preserve => true, :verbose => true
390
+ end
391
+ result
392
+ end
393
+
394
+ def mangled_old_xib_name(xib, lang)
395
+ file = File.split(xib).last
396
+ File.join($config['old_xibs_folder'], "#{$config['prefix']}_OLD_#{lang}_#{file}")
397
+ end
398
+
399
+ def old_xib_for_xib_lang(xib, lang)
400
+ mangled_old_xib_filename = mangled_old_xib_name(xib, lang)
401
+ end
402
+
403
+ main
404
+ exit 0
data/lib/babelyoda.rb ADDED
File without changes
metadata ADDED
@@ -0,0 +1,92 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: babelyoda
3
+ version: !ruby/object:Gem::Version
4
+ prerelease: false
5
+ segments:
6
+ - 1
7
+ - 2
8
+ - 0
9
+ version: 1.2.0
10
+ platform: ruby
11
+ authors:
12
+ - Andrey Subbotin
13
+ autorequire:
14
+ bindir: bin
15
+ cert_chain: []
16
+
17
+ date: 2010-04-20 00:00:00 +04:00
18
+ default_executable:
19
+ - babelyoda
20
+ dependencies:
21
+ - !ruby/object:Gem::Dependency
22
+ name: plist
23
+ prerelease: false
24
+ requirement: &id001 !ruby/object:Gem::Requirement
25
+ requirements:
26
+ - - ">="
27
+ - !ruby/object:Gem::Version
28
+ segments:
29
+ - 0
30
+ version: "0"
31
+ type: :runtime
32
+ version_requirements: *id001
33
+ - !ruby/object:Gem::Dependency
34
+ name: term-ansicolor
35
+ prerelease: false
36
+ requirement: &id002 !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ segments:
41
+ - 0
42
+ version: "0"
43
+ type: :runtime
44
+ version_requirements: *id002
45
+ description: A simple utility to push/pull l10n resources of an iPhone project to/from the translators
46
+ email: andrey@subbotin.me
47
+ executables:
48
+ - babelyoda
49
+ extensions: []
50
+
51
+ extra_rdoc_files:
52
+ - LICENSE
53
+ - README.rdoc
54
+ files:
55
+ - CHANGELOG
56
+ - LICENSE
57
+ - README.rdoc
58
+ - VERSION
59
+ - bin/babelyoda
60
+ - lib/babelyoda.rb
61
+ has_rdoc: true
62
+ homepage: http://github.com/eploko/babelyoda
63
+ licenses: []
64
+
65
+ post_install_message:
66
+ rdoc_options:
67
+ - --charset=UTF-8
68
+ require_paths:
69
+ - lib
70
+ required_ruby_version: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - ">="
73
+ - !ruby/object:Gem::Version
74
+ segments:
75
+ - 0
76
+ version: "0"
77
+ required_rubygems_version: !ruby/object:Gem::Requirement
78
+ requirements:
79
+ - - ">="
80
+ - !ruby/object:Gem::Version
81
+ segments:
82
+ - 0
83
+ version: "0"
84
+ requirements: []
85
+
86
+ rubyforge_project:
87
+ rubygems_version: 1.3.6
88
+ signing_key:
89
+ specification_version: 3
90
+ summary: iPhone project localization made easy
91
+ test_files: []
92
+