vocab 0.0.1

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 (60) hide show
  1. data/.gitignore +9 -0
  2. data/Gemfile +4 -0
  3. data/README.md +58 -0
  4. data/Rakefile +13 -0
  5. data/bin/vocab +11 -0
  6. data/lib/vocab/application.rb +58 -0
  7. data/lib/vocab/extractor/android.rb +56 -0
  8. data/lib/vocab/extractor/base.rb +92 -0
  9. data/lib/vocab/extractor/rails.rb +81 -0
  10. data/lib/vocab/extractor.rb +7 -0
  11. data/lib/vocab/merger/android.rb +59 -0
  12. data/lib/vocab/merger/base.rb +21 -0
  13. data/lib/vocab/merger/rails.rb +105 -0
  14. data/lib/vocab/merger.rb +7 -0
  15. data/lib/vocab/settings.rb +35 -0
  16. data/lib/vocab/translator/android.rb +43 -0
  17. data/lib/vocab/translator/base.rb +12 -0
  18. data/lib/vocab/translator/rails.rb +67 -0
  19. data/lib/vocab/translator.rb +7 -0
  20. data/lib/vocab/ui.rb +19 -0
  21. data/lib/vocab/validator/android.rb +23 -0
  22. data/lib/vocab/validator/base.rb +40 -0
  23. data/lib/vocab/validator/rails.rb +31 -0
  24. data/lib/vocab/validator.rb +7 -0
  25. data/lib/vocab/version.rb +3 -0
  26. data/lib/vocab.rb +36 -0
  27. data/spec/application_spec.rb +20 -0
  28. data/spec/data/android/current.xml +8 -0
  29. data/spec/data/android/locales/values/strings.xml +10 -0
  30. data/spec/data/android/locales/values-es/strings.xml +6 -0
  31. data/spec/data/android/locales/values-large-hdpi/dimens.xml +1 -0
  32. data/spec/data/android/locales/values-large-hdpi/styles.xml +1 -0
  33. data/spec/data/android/previous.xml +6 -0
  34. data/spec/data/android/translations/values-es/es-string-file.xml +7 -0
  35. data/spec/data/android/write.xml +6 -0
  36. data/spec/data/rails/locales/en.yml +10 -0
  37. data/spec/data/rails/locales/es.yml +6 -0
  38. data/spec/data/rails/locales/models/product/en.yml +13 -0
  39. data/spec/data/rails/locales/models/product/es.yml +13 -0
  40. data/spec/data/rails/translations/cn.yml +21 -0
  41. data/spec/data/rails/translations/es.yml +12 -0
  42. data/spec/extractor/android_spec.rb +95 -0
  43. data/spec/extractor/base_spec.rb +134 -0
  44. data/spec/extractor/rails_spec.rb +116 -0
  45. data/spec/merger/android_spec.rb +138 -0
  46. data/spec/merger/base_spec.rb +24 -0
  47. data/spec/merger/rails_spec.rb +183 -0
  48. data/spec/spec_helper.rb +23 -0
  49. data/spec/support/matchers.rb +13 -0
  50. data/spec/support/shared.rb +6 -0
  51. data/spec/tmp/.gitkeep +0 -0
  52. data/spec/translator/android_spec.rb +24 -0
  53. data/spec/translator/rails_spec.rb +155 -0
  54. data/spec/ui_spec.rb +15 -0
  55. data/spec/validator/android_spec.rb +54 -0
  56. data/spec/validator/base_spec.rb +5 -0
  57. data/spec/validator/rails_spec.rb +83 -0
  58. data/spec/vocab_spec.rb +13 -0
  59. data/vocab.gemspec +25 -0
  60. metadata +171 -0
data/.gitignore ADDED
@@ -0,0 +1,9 @@
1
+ *.gem
2
+ *.tmp
3
+ .bundle
4
+ Gemfile.lock
5
+ pkg/*
6
+ spec/tmp/*
7
+ !spec/tmp/.vocab
8
+ spec/data/android/versions/*.tmp
9
+ tmp/last_translation/*
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in vocab.gemspec
4
+ gemspec
data/README.md ADDED
@@ -0,0 +1,58 @@
1
+ # Description
2
+
3
+ Command line tool to automate the manipulation of i18n string files in Rails and Android.
4
+
5
+ Features include:
6
+
7
+ * Generate a list all English translations that have been updated or added since the last translation
8
+ * Integrate a list of newly translated strings into the right place in the project translation file(s)
9
+ * Validate that all languages have the same keys
10
+
11
+ # Requirements
12
+
13
+ * Must be used in context of git controlled project
14
+ * Assumes that android string files are called strings.xml
15
+
16
+ # Installation
17
+
18
+ Install the gem in your project
19
+
20
+ gem install vocab
21
+
22
+ Set the current git commit to be the starting point for the next translation diff
23
+
24
+ vocab init
25
+
26
+ # Usage
27
+
28
+ ## Extracting Changed and Updated Strings For Translation
29
+
30
+ vocab extract android
31
+
32
+ or
33
+
34
+ vocab extract rails
35
+
36
+ Note: keys starting with 'debug_' are ignored. Prepend development-only strings
37
+ with 'debug_' to avoid these keys being sent to translators.
38
+
39
+ ## Merging new translations into project string files
40
+
41
+ vocab merge android
42
+
43
+ or
44
+
45
+ vocab merge rails
46
+
47
+ ## Merging new translations into project string files
48
+
49
+ vocab validate android
50
+
51
+ or
52
+
53
+ vocab validate rails
54
+
55
+ # TODO
56
+
57
+ * Add .processed to each tmp/translation after success
58
+ * Accept filenames other than strings.xml under tmp/translation/values-xx/yyyy.xml
data/Rakefile ADDED
@@ -0,0 +1,13 @@
1
+ require "bundler/gem_tasks"
2
+
3
+ require "rspec/core/rake_task"
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ gemspec = eval(File.read("vocab.gemspec"))
7
+
8
+ task :build => "#{gemspec.full_name}.gem"
9
+
10
+ file "#{gemspec.full_name}.gem" => gemspec.files + ["vocab.gemspec"] do
11
+ system "gem build vocab.gemspec"
12
+ system "gem install vocab-#{Vocab::VERSION}.gem"
13
+ end
data/bin/vocab ADDED
@@ -0,0 +1,11 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ begin
4
+ require 'rubygems'
5
+ gem 'vocab'
6
+ rescue LoadError
7
+ end
8
+
9
+ require 'vocab'
10
+
11
+ Vocab::Application.run
@@ -0,0 +1,58 @@
1
+ require 'optparse'
2
+ require 'ostruct'
3
+
4
+ module Vocab
5
+ class Application
6
+
7
+ class << self
8
+ def run
9
+ handle_command
10
+ end
11
+
12
+ ##############################
13
+ # CLI
14
+ ##############################
15
+
16
+ def handle_command
17
+ options = OpenStruct.new
18
+ parser = OptionParser.new
19
+
20
+ parser.banner = 'Usage: vocab [-h] command [platform] [file]'
21
+ parser.on( '-h', '--help', 'Show this usage message' ) { options.help = true }
22
+ parser.separator ""
23
+ parser.separator " vocab init"
24
+ parser.separator " vocab extract rails"
25
+ parser.separator " vocab extract android"
26
+ parser.separator " vocab merge rails"
27
+ parser.separator " vocab merge android"
28
+ parser.separator " vocab validate android"
29
+ parser.separator " vocab validate rails"
30
+ parser.separator ""
31
+
32
+ commands = parser.parse( ARGV )
33
+ options.command = commands[0]
34
+ options.platform = commands[1]
35
+ options.path = commands[2]
36
+
37
+ if( options.command == 'init' )
38
+ Vocab::Settings.create
39
+ elsif( options.command == 'extract' && options.platform == 'rails' )
40
+ Extractor::Rails.extract
41
+ elsif( options.command == 'extract' && options.platform == 'android' )
42
+ Extractor::Android.extract
43
+ elsif( options.command == 'merge' && options.platform == 'rails' )
44
+ Merger::Rails.new.merge
45
+ elsif( options.command == 'merge' && options.platform == 'android' )
46
+ Merger::Android.new.merge
47
+ elsif( options.command == 'validate' && options.platform == 'android' )
48
+ Validator::Android.new.validate
49
+ elsif( options.command == 'validate' && options.platform == 'rails' )
50
+ Validator::Rails.new.validate
51
+ else
52
+ puts parser.help
53
+ end
54
+ end
55
+
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,56 @@
1
+ module Vocab
2
+ module Extractor
3
+ class Android < Base
4
+ class << self
5
+
6
+ DIFF = 'strings.diff.xml'
7
+ FULL = 'strings.full.xml'
8
+
9
+ def extract_current( path = nil )
10
+ path ||= "#{Vocab.root}/res/values/strings.xml"
11
+ return Vocab::Translator::Android.hash_from_xml( path )
12
+ end
13
+
14
+ def extract_previous( path = nil )
15
+ path ||= "res/values/strings.xml"
16
+ sha = Vocab.settings.last_translation
17
+ xml = previous_file( path, sha )
18
+
19
+ tmpfile = "#{Vocab.root}/tmp/last_translation/#{File.basename(path)}"
20
+ FileUtils.mkdir_p( File.dirname( tmpfile ) )
21
+ File.open( tmpfile, 'w' ) { |f| f.write( xml ) }
22
+ return Vocab::Translator::Android.hash_from_xml( tmpfile )
23
+ end
24
+
25
+ def write_diff( diff, path = nil )
26
+ path ||= "#{Vocab.root}/#{DIFF}"
27
+ Vocab::Translator::Android.write( diff, path )
28
+ end
29
+
30
+ def write_full( diff, path = nil )
31
+ path ||= "#{Vocab.root}/#{FULL}"
32
+ Vocab::Translator::Android.write( diff, path )
33
+ end
34
+
35
+ def examples( locales_dir = nil )
36
+ locales_dir ||= "#{Vocab.root}/res/values"
37
+ return Vocab::Translator::Android.locales( locales_dir ).collect do |locale|
38
+ "tmp/translations/values-#{locale}"
39
+ end
40
+ end
41
+
42
+ def print_instructions( values = {} )
43
+ values[ :diff ] = DIFF
44
+ values[ :full ] = FULL
45
+ values[ :tree ] = <<-EOS
46
+ tmp/translations/values-es/strings.xml
47
+ tmp/translations/values-zh-rCN/strings.xml
48
+ EOS
49
+
50
+ super( values )
51
+ end
52
+
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,92 @@
1
+ module Vocab
2
+ module Extractor
3
+ class Base
4
+ class << self
5
+ def extract( diff_path = nil, full_path = nil )
6
+ current = extract_current
7
+ previous = extract_previous
8
+ diff = diff( previous, current )
9
+ write_diff( diff, diff_path )
10
+ write_full( current, full_path )
11
+ mkdir_examples
12
+ print_instructions
13
+ end
14
+
15
+ # make a hash of all the translations that are new or changed in the current yml
16
+ def diff( previous, current )
17
+ diff = {}
18
+ current.each do |key, value|
19
+ next if Vocab::Translator::Base.ignore_key?( key )
20
+
21
+ previous_value = previous[ key ]
22
+ if( previous_value.nil? || previous_value != value )
23
+ diff[ key ] = value
24
+ end
25
+ end
26
+ return diff
27
+ end
28
+
29
+ def extract_previous
30
+ raise "extract_previous not implemented"
31
+ end
32
+
33
+ def extract_current
34
+ raise "extract_current not implemented"
35
+ end
36
+
37
+ def write_diff( diff, path )
38
+ raise "write_diff not implemented"
39
+ end
40
+
41
+ def write_full( diff, path )
42
+ raise "write_full not implemented"
43
+ end
44
+
45
+ def previous_file( path, sha )
46
+ path = git_path( path )
47
+ return `cd #{Vocab.root} && git show #{sha}:#{path}`
48
+ end
49
+
50
+ def git_root
51
+ return `git rev-parse --show-toplevel`.strip
52
+ end
53
+
54
+ def git_path( path )
55
+ return File.expand_path( path ).gsub( "#{git_root}/", '' )
56
+ end
57
+
58
+ def mkdir_examples
59
+ examples.each do |example|
60
+ Vocab.ui.say( "Creating placeholder: #{example}" )
61
+ FileUtils.mkdir_p( example )
62
+ end
63
+ end
64
+
65
+ def print_instructions( values = {} )
66
+ instructions = <<-EOS
67
+
68
+ Extraction completed. To complete the translation:
69
+
70
+ 1. Send the language files to the translators:
71
+
72
+ #{values[:diff]} for languages that are already in the app
73
+ #{values[:full]} for languages that need a complete translation
74
+
75
+ 2. To integrate new translations:
76
+
77
+ Place completed translations under tmp/translations, for example:
78
+
79
+ #{values[:tree]}
80
+
81
+ 3. Merge translations into the project with:
82
+
83
+ vocab merge rails
84
+
85
+ EOS
86
+ Vocab.ui.say( instructions )
87
+ end
88
+
89
+ end
90
+ end
91
+ end
92
+ end
@@ -0,0 +1,81 @@
1
+ module Vocab
2
+ module Extractor
3
+ class Rails < Base
4
+ DIFF = 'en.yml'
5
+ FULL = 'en.full.yml'
6
+
7
+ class << self
8
+ def write_diff( diff, path )
9
+ path ||= "#{Vocab.root}/#{DIFF}"
10
+ write( diff, path )
11
+ end
12
+
13
+ def write_full( full, path )
14
+ path ||= "#{Vocab.root}/#{FULL}"
15
+ write( full, path )
16
+ end
17
+
18
+ def write( translations, path )
19
+ data = hasherize( translations ).to_yaml
20
+ File.open( path, "w+" ) { |f| f.write( data ) }
21
+ Vocab.ui.say( "Extracted to #{path}" )
22
+ end
23
+
24
+ def extract_previous( locales_root = nil )
25
+ locales_root ||= "config/locales"
26
+ tmpdir = "#{Vocab.root}/tmp/last_translation"
27
+ FileUtils.rm_rf( "#{tmpdir}/*" )
28
+
29
+ sha = Vocab.settings.last_translation
30
+ translation_files = `git ls-tree --name-only -r #{sha}:#{locales_root}`.split( "\n" )
31
+ translation_files = translation_files.select { |f| f =~ /en.(yml|rb)$/ }
32
+ translation_files.each do |path|
33
+ tmpdir_path = "#{tmpdir}/#{path}"
34
+ FileUtils.mkdir_p( File.dirname( tmpdir_path ) )
35
+ File.open( tmpdir_path, "w+" ) do |f|
36
+ yml = previous_file( "#{locales_root}/#{path}", sha )
37
+ f.write( yml )
38
+ end
39
+ end
40
+
41
+ return translations( tmpdir )
42
+ end
43
+
44
+ def extract_current( locales_root = nil )
45
+ locales_root ||= "#{Vocab.root}/config/locales"
46
+ return translations( locales_root )
47
+ end
48
+
49
+ def translations( dir )
50
+ translator = Vocab::Translator::Rails.new
51
+ translator.load_dir( dir )
52
+ return translator.flattened_translations( :prefix => true )
53
+ end
54
+
55
+ def hasherize( diff )
56
+ translator = Vocab::Translator::Rails.new
57
+ diff.each do |key, value|
58
+ key = key.to_s.gsub!( /^en\./, '' )
59
+ translator.store( key, value )
60
+ end
61
+ return translator.translations( :prefix => true )
62
+ end
63
+
64
+ def examples
65
+ return [ "#{Vocab.root}/tmp/translations" ]
66
+ end
67
+
68
+ def print_instructions( values = {} )
69
+ values[ :diff ] = DIFF
70
+ values[ :full ] = FULL
71
+ values[ :tree ] = <<-EOS
72
+ tmp/translations/es.yml
73
+ tmp/translations/zh.yml
74
+ EOS
75
+
76
+ super( values )
77
+ end
78
+ end
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,7 @@
1
+ module Vocab
2
+ module Extractor
3
+ autoload :Base, 'vocab/extractor/base'
4
+ autoload :Rails, 'vocab/extractor/rails'
5
+ autoload :Android, 'vocab/extractor/android'
6
+ end
7
+ end
@@ -0,0 +1,59 @@
1
+ module Vocab
2
+ module Merger
3
+ class Android < Base
4
+
5
+ def initialize( locales_dir = nil, updates_dir = nil )
6
+ @locales_dir = locales_dir || 'res'
7
+ @updates_dir = updates_dir || 'tmp/translations'
8
+ end
9
+
10
+ def merge_file( path )
11
+ keys = english_keys
12
+ current = current_for_locale( path )
13
+ updates = updates_for_locale( path )
14
+
15
+ translation = {}
16
+ keys.each do |key|
17
+ next if Vocab::Translator::Base.ignore_key?( key )
18
+
19
+ value = updates[ key ] || current[ key ]
20
+ if value
21
+ translation[ key ] = value
22
+ else
23
+ Vocab.ui.warn( "No translation found for key #{key} while merging #{path}" )
24
+ end
25
+ end
26
+
27
+ Vocab::Translator::Android.write( translation, path )
28
+ end
29
+
30
+ def english_keys
31
+ return Vocab::Translator::Android.english_keys( @locales_dir )
32
+ end
33
+
34
+ def current_for_locale( path )
35
+ Vocab::Translator::Android.hash_from_xml( path )
36
+ end
37
+
38
+ def updates_for_locale( path )
39
+ name = path.gsub( "#{@locales_dir}/", '' )
40
+ dirname = File.dirname( name )
41
+ entries = Dir.glob( "#{updates_dir}/#{dirname}/*.xml" )
42
+ Vocab.ui.warn( "Multiple update files for #{path}: #{entries.join( ',' )}" ) if entries.size > 1
43
+ update = entries.first
44
+ Vocab::Translator::Android.hash_from_xml( update )
45
+ end
46
+
47
+ def translation_locales
48
+ return Vocab::Translator::Android.locales( @updates_dir, false )
49
+ end
50
+
51
+ def files_to_merge
52
+ return translation_locales.collect do |locale|
53
+ "#{@locales_dir}/values-#{locale}/strings.xml"
54
+ end
55
+ end
56
+
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,21 @@
1
+ module Vocab
2
+ module Merger
3
+ class Base
4
+
5
+ attr_accessor :locales_dir, :updates_dir
6
+
7
+ def update_settings
8
+ sha = Vocab.settings.update_translation
9
+ Vocab.ui.say( "Updated current translation to #{sha}" )
10
+ end
11
+
12
+ def merge
13
+ files_to_merge.each do |file|
14
+ Vocab.ui.say( "Merging file: #{file}" )
15
+ merge_file( file )
16
+ end
17
+ update_settings
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,105 @@
1
+ module Vocab
2
+ module Merger
3
+ class Rails < Base
4
+
5
+ def initialize( locales_dir = nil, updates_dir = nil )
6
+ @locales_dir = locales_dir || 'config/locales'
7
+ @updates_dir = updates_dir || 'tmp/translations'
8
+ end
9
+
10
+ def merge_file( locales_path )
11
+ return unless translatable?( locales_path )
12
+ create_if_missing( locales_path )
13
+
14
+ # list of keys that need to be in the translated file
15
+ keys = Vocab::Merger::Rails.keys_for_file( locales_path )
16
+
17
+ # existing translations already in the file
18
+ locales_translator = translator( locales_path )
19
+ locales = locales_translator.flattened_translations
20
+
21
+ # new translations from the translators
22
+ update_path = "#{@updates_dir}/#{locales_translator.locale}.yml"
23
+ unless File.exists?( update_path )
24
+ Vocab.ui.say( "Missing update file: #{update_path} to translate #{locales_path}" )
25
+ return
26
+ end
27
+ updates_translator = translator( update_path )
28
+ updates = updates_translator.flattened_translations
29
+
30
+ # apply updated keys to locales hash
31
+ keys.each do |key|
32
+ next if Vocab::Translator::Base.ignore_key?( key )
33
+
34
+ value = updates[ key ] || locales[ key ]
35
+ if value
36
+ locales_translator.store( key, value )
37
+ else
38
+ Vocab.ui.warn( "No translation found for key #{key} while merging #{locales_path}" )
39
+ end
40
+ end
41
+
42
+ locales_translator.write_file( locales_path )
43
+ end
44
+
45
+ def self.keys_for_file( path )
46
+ en_path = Vocab::Translator::Rails.en_equivalent_path( path )
47
+ translator = Vocab::Translator::Rails.new
48
+ translator.load_file( en_path )
49
+ return translator.flattened_translations.keys
50
+ end
51
+
52
+ def translatable?( path )
53
+ if File.basename( path ) == 'en.yml'
54
+ Vocab.ui.warn( "can't translate english file #{path}" )
55
+ return false
56
+ end
57
+
58
+ unless File.exists?( Vocab::Translator::Rails.en_equivalent_path( path ) )
59
+ Vocab.ui.warn( "skipping because no english equivalent for #{path}" )
60
+ return false
61
+ end
62
+
63
+ if( File.exists?( path ) )
64
+ extension = File.basename( path, '.yml' )
65
+ contents = YAML.load_file( path ).keys.first.to_s
66
+ if( extension != contents )
67
+ Vocab.ui.warn( "File extension does not match file contents in #{path}" )
68
+ return false
69
+ end
70
+ end
71
+
72
+ return true
73
+ end
74
+
75
+ def translator( path )
76
+ translator = Vocab::Translator::Rails.new
77
+ translator.load_file( path ) if File.exists?( path )
78
+ return translator
79
+ end
80
+
81
+ def create_if_missing( path )
82
+ return if File.exists?( path )
83
+ locale = File.basename( path, '.yml' )
84
+ Vocab.ui.say( "Creating file #{path}" )
85
+ File.open( path, 'w+' ) { |file| file.write( { locale => {} }.to_yaml ) }
86
+ end
87
+
88
+ # TODO cache this so you don't hit the FS so much
89
+ def translation_locales
90
+ return Dir.glob( "#{@updates_dir}/*.yml" ).collect { |f| File.basename( f, '.yml' ) }
91
+ end
92
+
93
+ def files_to_merge
94
+ paths = []
95
+ Dir.glob( "#{@locales_dir}/**/en.yml" ).each do |en_path|
96
+ translation_locales.each do |locale|
97
+ paths << en_path.gsub( /en.yml$/, "#{locale}.yml" )
98
+ end
99
+ end
100
+ return paths
101
+ end
102
+
103
+ end
104
+ end
105
+ end
@@ -0,0 +1,7 @@
1
+ module Vocab
2
+ module Merger
3
+ autoload :Base, 'vocab/merger/base'
4
+ autoload :Rails, 'vocab/merger/rails'
5
+ autoload :Android, 'vocab/merger/android'
6
+ end
7
+ end
@@ -0,0 +1,35 @@
1
+ module Vocab
2
+ class Settings
3
+ def initialize( root )
4
+ @root = root
5
+ @local_config = File.exist?( config_file ) ? YAML.load_file( config_file ) : {}
6
+ end
7
+
8
+ def config_file
9
+ Pathname.new( "#{@root}/.vocab" )
10
+ end
11
+
12
+ def last_translation
13
+ return @local_config[ 'last_translation' ]
14
+ end
15
+
16
+ def update_translation
17
+ current_sha = `git rev-parse HEAD`.strip
18
+ @local_config[ 'last_translation' ] = current_sha
19
+ write_settings
20
+ return current_sha
21
+ end
22
+
23
+ def write_settings
24
+ File.open( config_file, 'w' ) { |f| f.write( @local_config.to_yaml ) }
25
+ `git add #{config_file}`
26
+ end
27
+
28
+ def self.create
29
+ Vocab.ui.say( "Writing new .vocab file. Check this file into your project repo" )
30
+ settings = Vocab::Settings.new( Dir.pwd )
31
+ settings.update_translation
32
+ settings.write_settings
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,43 @@
1
+ module Vocab
2
+ module Translator
3
+ class Android < Base
4
+
5
+ def self.hash_from_xml( path )
6
+ xml = File.open( path ) { |f| f.read }
7
+ doc = Nokogiri::HTML.fragment( xml ) { |config| config.noblanks }
8
+ children = doc.search( 'resources/string' )
9
+ hash = {}
10
+ children.each { |child| hash[ child['name'] ] = child.text }
11
+ return hash
12
+ end
13
+
14
+ def self.write( hash, path )
15
+ builder = Nokogiri::XML::Builder.new do |xml|
16
+ xml.resources {
17
+ hash.sort.each do |key, value|
18
+ xml.string( value, :name => key )
19
+ end
20
+ }
21
+ end
22
+ File.open( path, 'w' ) { |f| f.write( builder.to_xml( :encoding => 'UTF-8' ) ) }
23
+ end
24
+
25
+ def self.english_keys( locales_dir )
26
+ path = "#{locales_dir}/values/strings.xml"
27
+ translations = Vocab::Translator::Android.hash_from_xml( path )
28
+ return translations.keys
29
+ end
30
+
31
+ def self.locales( dir, strict = true )
32
+ xml_pattern = strict ? 'strings.xml' : '*'
33
+
34
+ locales = []
35
+ Dir.glob( "#{dir}/values-*/#{xml_pattern}" ).each do |path|
36
+ locales << $1 if path =~ /values-(.*)\//
37
+ end
38
+ return locales
39
+ end
40
+
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,12 @@
1
+ module Vocab
2
+ module Translator
3
+ class Base
4
+
5
+ def self.ignore_key?( key )
6
+ return true if key.to_s.start_with?( 'debug_' )
7
+ return false
8
+ end
9
+
10
+ end
11
+ end
12
+ end