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.
- data/.gitignore +9 -0
- data/Gemfile +4 -0
- data/README.md +58 -0
- data/Rakefile +13 -0
- data/bin/vocab +11 -0
- data/lib/vocab/application.rb +58 -0
- data/lib/vocab/extractor/android.rb +56 -0
- data/lib/vocab/extractor/base.rb +92 -0
- data/lib/vocab/extractor/rails.rb +81 -0
- data/lib/vocab/extractor.rb +7 -0
- data/lib/vocab/merger/android.rb +59 -0
- data/lib/vocab/merger/base.rb +21 -0
- data/lib/vocab/merger/rails.rb +105 -0
- data/lib/vocab/merger.rb +7 -0
- data/lib/vocab/settings.rb +35 -0
- data/lib/vocab/translator/android.rb +43 -0
- data/lib/vocab/translator/base.rb +12 -0
- data/lib/vocab/translator/rails.rb +67 -0
- data/lib/vocab/translator.rb +7 -0
- data/lib/vocab/ui.rb +19 -0
- data/lib/vocab/validator/android.rb +23 -0
- data/lib/vocab/validator/base.rb +40 -0
- data/lib/vocab/validator/rails.rb +31 -0
- data/lib/vocab/validator.rb +7 -0
- data/lib/vocab/version.rb +3 -0
- data/lib/vocab.rb +36 -0
- data/spec/application_spec.rb +20 -0
- data/spec/data/android/current.xml +8 -0
- data/spec/data/android/locales/values/strings.xml +10 -0
- data/spec/data/android/locales/values-es/strings.xml +6 -0
- data/spec/data/android/locales/values-large-hdpi/dimens.xml +1 -0
- data/spec/data/android/locales/values-large-hdpi/styles.xml +1 -0
- data/spec/data/android/previous.xml +6 -0
- data/spec/data/android/translations/values-es/es-string-file.xml +7 -0
- data/spec/data/android/write.xml +6 -0
- data/spec/data/rails/locales/en.yml +10 -0
- data/spec/data/rails/locales/es.yml +6 -0
- data/spec/data/rails/locales/models/product/en.yml +13 -0
- data/spec/data/rails/locales/models/product/es.yml +13 -0
- data/spec/data/rails/translations/cn.yml +21 -0
- data/spec/data/rails/translations/es.yml +12 -0
- data/spec/extractor/android_spec.rb +95 -0
- data/spec/extractor/base_spec.rb +134 -0
- data/spec/extractor/rails_spec.rb +116 -0
- data/spec/merger/android_spec.rb +138 -0
- data/spec/merger/base_spec.rb +24 -0
- data/spec/merger/rails_spec.rb +183 -0
- data/spec/spec_helper.rb +23 -0
- data/spec/support/matchers.rb +13 -0
- data/spec/support/shared.rb +6 -0
- data/spec/tmp/.gitkeep +0 -0
- data/spec/translator/android_spec.rb +24 -0
- data/spec/translator/rails_spec.rb +155 -0
- data/spec/ui_spec.rb +15 -0
- data/spec/validator/android_spec.rb +54 -0
- data/spec/validator/base_spec.rb +5 -0
- data/spec/validator/rails_spec.rb +83 -0
- data/spec/vocab_spec.rb +13 -0
- data/vocab.gemspec +25 -0
- metadata +171 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
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,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,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
|
data/lib/vocab/merger.rb
ADDED
@@ -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
|