babelyoda 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
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
+