vocab 0.0.1

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