vocab 0.0.6 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/lib/vocab/application.rb +24 -3
- data/lib/vocab/cleaner/android.rb +15 -0
- data/lib/vocab/cleaner/base.rb +14 -0
- data/lib/vocab/cleaner/rails.rb +141 -0
- data/lib/vocab/cleaner.rb +7 -0
- data/lib/vocab/converter/base.rb +17 -0
- data/lib/vocab/converter/rails.rb +86 -0
- data/lib/vocab/converter.rb +6 -0
- data/lib/vocab/extractor/rails.rb +26 -10
- data/lib/vocab/merger/android.rb +39 -3
- data/lib/vocab/merger/rails.rb +18 -0
- data/lib/vocab/translator/rails.rb +4 -0
- data/lib/vocab/version.rb +1 -1
- data/lib/vocab.rb +9 -0
- data/spec/cleaner/android_spec.rb +0 -0
- data/spec/cleaner/base_spec.rb +19 -0
- data/spec/cleaner/rails_spec.rb +79 -0
- data/spec/converter/base_spec.rb +23 -0
- data/spec/converter/rails_spec.rb +32 -0
- data/spec/data/android/locales/values/strings.xml +1 -1
- data/spec/data/android/locales/values-fr/strings.xml +21 -0
- data/spec/data/rails/full/cn.full.yml +0 -0
- data/spec/data/rails/full/en.expected.yml +4 -0
- data/spec/data/rails/full/en.full.yml +4 -0
- data/spec/data/rails/full/es.expected.yml +8 -0
- data/spec/data/rails/full/es.full.yml +8 -0
- data/spec/data/rails/full/fr.expected.yml +5 -0
- data/spec/data/rails/full/fr.full.yml +11 -0
- data/spec/data/rails/full/test.diff.yml +4 -0
- data/spec/data/rails/xml/in_file.xml +16 -0
- data/spec/data/rails/xml/in_file.yml +19 -0
- data/spec/data/rails/xml/out_file_expected.xml +16 -0
- data/spec/data/rails/xml/out_file_expected.yml +19 -0
- data/spec/data/rails/xml/test_file.xml +16 -0
- data/spec/data/rails/xml/test_file.yml +19 -0
- data/spec/extractor/android_spec.rb +2 -2
- data/spec/extractor/rails_spec.rb +15 -0
- data/spec/merger/android_spec.rb +13 -0
- data/spec/merger/rails_spec.rb +7 -0
- data/spec/validator/android_spec.rb +2 -2
- data/vocab.gemspec +4 -0
- metadata +142 -11
data/lib/vocab/application.rb
CHANGED
@@ -18,12 +18,16 @@ module Vocab
|
|
18
18
|
options = OpenStruct.new
|
19
19
|
parser = OptionParser.new
|
20
20
|
|
21
|
-
parser.banner = 'Usage: vocab [-h] command [platform] [
|
21
|
+
parser.banner = 'Usage: vocab [-h] command [platform] [type] [path]'
|
22
22
|
parser.on( '-h', '--help', 'Show this usage message' ) { options.help = true }
|
23
23
|
parser.separator ""
|
24
24
|
parser.separator " vocab init"
|
25
25
|
parser.separator " vocab extract rails"
|
26
|
+
parser.separator " vocab extract rails all"
|
26
27
|
parser.separator " vocab extract android"
|
28
|
+
parser.separator " vocab clean rails"
|
29
|
+
parser.separator " vocab convert rails xml2yml <infile>"
|
30
|
+
parser.separator " vocab convert rails yml2xml <infile>"
|
27
31
|
parser.separator " vocab merge rails"
|
28
32
|
parser.separator " vocab merge android"
|
29
33
|
parser.separator " vocab validate android"
|
@@ -33,12 +37,29 @@ module Vocab
|
|
33
37
|
commands = parser.parse( ARGV )
|
34
38
|
options.command = commands[0]
|
35
39
|
options.platform = commands[1]
|
36
|
-
options.
|
40
|
+
options.type = commands[2]
|
41
|
+
options.path = commands[3]
|
37
42
|
|
38
43
|
if( options.command == 'init' )
|
39
44
|
Vocab::Settings.create
|
45
|
+
elsif( options.command == 'clean' && options.platform == 'rails' )
|
46
|
+
Cleaner::Rails.clean
|
47
|
+
elsif( options.command == 'clean' && options.platform == 'android' )
|
48
|
+
Cleaner::Android.clean
|
49
|
+
elsif( options.command == 'convert' && options.platform == 'rails' )
|
50
|
+
if options.type == 'xml2yml'
|
51
|
+
Converter::Rails.convert_xml_to_yml( options.path )
|
52
|
+
elsif options.type = 'yml2xml'
|
53
|
+
Converter::Rails.convert_yml_to_xml( options.path )
|
54
|
+
else
|
55
|
+
puts parser.help
|
56
|
+
end
|
40
57
|
elsif( options.command == 'extract' && options.platform == 'rails' )
|
41
|
-
|
58
|
+
if options.type == 'all'
|
59
|
+
Extractor::Rails.extract_all
|
60
|
+
else
|
61
|
+
Extractor::Rails.extract
|
62
|
+
end
|
42
63
|
elsif( options.command == 'extract' && options.platform == 'android' )
|
43
64
|
Extractor::Android.extract
|
44
65
|
elsif( options.command == 'merge' && options.platform == 'rails' )
|
@@ -0,0 +1,141 @@
|
|
1
|
+
# Cleans full translation files ending in "full.yml" in the vocab root directory by:
|
2
|
+
# - removing empty keys
|
3
|
+
# - replacing HTML codes with the corresponding UTF-8 characters (e.g. > --> > )
|
4
|
+
# - replacing \x[XX]\x[XX] codes with characters
|
5
|
+
# - removing keys that shouldn't be translated (specified in a blacklist)
|
6
|
+
|
7
|
+
module Vocab
|
8
|
+
module Cleaner
|
9
|
+
class Rails < Base
|
10
|
+
FULL_SUFFIX = 'full.yml'
|
11
|
+
DIFF_SUFFIX = 'diff.yml'
|
12
|
+
CLEAN_SUFFIX = 'clean.yml'
|
13
|
+
BLACKLIST = [
|
14
|
+
'devise',
|
15
|
+
'active_record',
|
16
|
+
'activerecord',
|
17
|
+
'number',
|
18
|
+
'datetime'
|
19
|
+
]
|
20
|
+
WINDOWS_TO_UTF8 = {
|
21
|
+
"\\xC2\\x80" => "\xe2\x82\xac", # EURO SIGN
|
22
|
+
"\\xC2\\x82" => "\xe2\x80\x9a", # SINGLE LOW-9 QUOTATION MARK
|
23
|
+
"\\xC2\\x83" => "\xc6\x92", # LATIN SMALL LETTER F WITH HOOK
|
24
|
+
"\\xC2\\x84" => "\xe2\x80\x9e", # DOUBLE LOW-9 QUOTATION MARK
|
25
|
+
"\\xC2\\x85" => "\xe2\x80\xa6", # HORIZONTAL ELLIPSIS
|
26
|
+
"\\xC2\\x86" => "\xe2\x80\xa0", # DAGGER
|
27
|
+
"\\xC2\\x87" => "\xe2\x80\xa1", # DOUBLE DAGGER
|
28
|
+
"\\xC2\\x88" => "\xcb\x86", # MODIFIER LETTER CIRCUMFLEX ACCENT
|
29
|
+
"\\xC2\\x89" => "\xe2\x80\xb0", # PER MILLE SIGN
|
30
|
+
"\\xC2\\x8A" => "\xc5\xa0", # LATIN CAPITAL LETTER S WITH CARON
|
31
|
+
"\\xC2\\x8B" => "\xe2\x80\xb9", # SINGLE LEFT-POINTING ANGLE QUOTATION
|
32
|
+
"\\xC2\\x8C" => "\xc5\x92", # LATIN CAPITAL LIGATURE OE
|
33
|
+
"\\xC2\\x8E" => "\xc5\xbd", # LATIN CAPITAL LETTER Z WITH CARON
|
34
|
+
"\\xC2\\x91" => "\xe2\x80\x98", # LEFT SINGLE QUOTATION MARK
|
35
|
+
"\\xC2\\x92" => "\xe2\x80\x99", # RIGHT SINGLE QUOTATION MARK
|
36
|
+
"\\xC2\\x93" => "\xe2\x80\x9c", # LEFT DOUBLE QUOTATION MARK
|
37
|
+
"\\xC2\\x94" => "\xe2\x80\x9d", # RIGHT DOUBLE QUOTATION MARK
|
38
|
+
"\\xC2\\x95" => "\xe2\x80\xa2", # BULLET
|
39
|
+
"\\xC2\\x96" => "\xe2\x80\x93", # EN DASH
|
40
|
+
"\\xC2\\x97" => "\xe2\x80\x94", # EM DASH
|
41
|
+
|
42
|
+
"\\xC2\\x98" => "\xcb\x9c", # SMALL TILDE
|
43
|
+
"\\xC2\\x99" => "\xe2\x84\xa2", # TRADE MARK SIGN
|
44
|
+
"\\xC2\\x9A" => "\xc5\xa1", # LATIN SMALL LETTER S WITH CARON
|
45
|
+
"\\xC2\\x9B" => "\xe2\x80\xba", # SINGLE RIGHT-POINTING ANGLE QUOTATION
|
46
|
+
"\\xC2\\x9C" => "\xc5\x93", # LATIN SMALL LIGATURE OE
|
47
|
+
"\\xC2\\x9E" => "\xc5\xbe", # LATIN SMALL LETTER Z WITH CARON
|
48
|
+
"\\xC2\\x9F" => "\xc5\xb8" # LATIN CAPITAL LETTER Y WITH DIAERESIS
|
49
|
+
}
|
50
|
+
|
51
|
+
class << self
|
52
|
+
|
53
|
+
def clean_file( file )
|
54
|
+
@file = file
|
55
|
+
@locale_name = File.basename( @file, '.yml' )
|
56
|
+
@clean_dir = File.dirname( @file )
|
57
|
+
@clean_name = "#{@clean_dir}/#{@locale_name}.#{CLEAN_SUFFIX}"
|
58
|
+
|
59
|
+
replace_codes
|
60
|
+
clean_yaml
|
61
|
+
end
|
62
|
+
|
63
|
+
def files_to_clean ( dir = Vocab.root )
|
64
|
+
return ( Dir.glob( "#{dir}/*.#{FULL_SUFFIX}" ) + Dir.glob( "#{dir}/*.#{DIFF_SUFFIX}" ) )
|
65
|
+
end
|
66
|
+
|
67
|
+
private
|
68
|
+
def replace_codes
|
69
|
+
translation = File.read( @file )
|
70
|
+
|
71
|
+
cleaned_text = replace_html_codes( translation )
|
72
|
+
cleaned_text = replace_windows_codes( cleaned_text )
|
73
|
+
|
74
|
+
cleaned_file = File.open( @clean_name, 'w' )
|
75
|
+
cleaned_file.puts( cleaned_text )
|
76
|
+
cleaned_file.close
|
77
|
+
end
|
78
|
+
|
79
|
+
def replace_html_codes( translation )
|
80
|
+
entity_matcher = /&.+?;/
|
81
|
+
coder = HTMLEntities.new( :expanded )
|
82
|
+
|
83
|
+
cleaned_text = translation.gsub( entity_matcher ) do |entity|
|
84
|
+
entity == """ ? "\\\"" : coder.decode( entity )
|
85
|
+
end
|
86
|
+
|
87
|
+
return cleaned_text
|
88
|
+
end
|
89
|
+
|
90
|
+
def replace_windows_codes( translation )
|
91
|
+
WINDOWS_TO_UTF8.each { |windows_code,utf8_code| translation.gsub!( windows_code, utf8_code ) }
|
92
|
+
return translation
|
93
|
+
end
|
94
|
+
|
95
|
+
def clean_yaml
|
96
|
+
# TODO: use psych
|
97
|
+
original_engine = YAML::ENGINE.yamler
|
98
|
+
YAML::ENGINE.yamler='syck'
|
99
|
+
translation_hash = YAML.load( File.open( @clean_name, 'r' ))
|
100
|
+
|
101
|
+
cleaned_file = File.open( @clean_name, 'w' )
|
102
|
+
keys = clean_keys( translation_hash )
|
103
|
+
cleaned_file.puts( replace_hex_codes( keys ) )
|
104
|
+
cleaned_file.close
|
105
|
+
|
106
|
+
ensure
|
107
|
+
YAML::ENGINE.yamler=original_engine
|
108
|
+
|
109
|
+
end
|
110
|
+
|
111
|
+
def replace_hex_codes( keys )
|
112
|
+
# Using ya2yaml, if available, for UTF8 support
|
113
|
+
keys.respond_to?( :ya2yaml ) ? keys.ya2yaml( :escape_as_utf8 => true ) : keys.to_yaml
|
114
|
+
end
|
115
|
+
|
116
|
+
def clean_keys( hash )
|
117
|
+
hash.inject({}) { |result, (key, value)|
|
118
|
+
if valid_key?( key )
|
119
|
+
value = clean_keys(value) if value.is_a? Hash
|
120
|
+
unless value.to_s.empty? or value == {}
|
121
|
+
result[(key.to_s rescue key) || key] = value
|
122
|
+
end
|
123
|
+
end
|
124
|
+
result
|
125
|
+
}
|
126
|
+
end
|
127
|
+
|
128
|
+
def valid_key?( key )
|
129
|
+
BLACKLIST.each do |prefix|
|
130
|
+
if key.to_s.include?( prefix )
|
131
|
+
return false
|
132
|
+
end
|
133
|
+
end
|
134
|
+
return true
|
135
|
+
end
|
136
|
+
|
137
|
+
|
138
|
+
end
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module Vocab
|
2
|
+
module Converter
|
3
|
+
class Base
|
4
|
+
class << self
|
5
|
+
def convert_xml_to_yml( path = nil)
|
6
|
+
Vocab.ui.say( "No conversion available" )
|
7
|
+
return nil
|
8
|
+
end
|
9
|
+
|
10
|
+
def convert_yml_to_xml( path = nil)
|
11
|
+
Vocab.ui.say( "No conversion available" )
|
12
|
+
return nil
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,86 @@
|
|
1
|
+
module Vocab
|
2
|
+
module Converter
|
3
|
+
class Rails < Base
|
4
|
+
SEPARATOR_ESCAPE_CHAR = "\001"
|
5
|
+
|
6
|
+
class << self
|
7
|
+
def convert_xml_to_yml( file = nil )
|
8
|
+
xml_root = 'hash'
|
9
|
+
keys = xml_to_yml_keys( Hash.from_xml( File.read( file ) )[ xml_root ] )
|
10
|
+
unwound_keys = unwind_keys( keys )
|
11
|
+
out_dir = File.dirname( file )
|
12
|
+
out_file = "#{out_dir}/#{File.basename( file, '.xml' ) + '.yml'}"
|
13
|
+
File.open( out_file, 'w' ) {|f| f.puts( ( keys_to_yaml( unwound_keys ) ) ) }
|
14
|
+
end
|
15
|
+
|
16
|
+
def xml_to_yml_keys( hash )
|
17
|
+
hash.inject( {} ) { |result, ( key, value )|
|
18
|
+
value = xml_to_yml_keys( value ) if value.is_a? Hash
|
19
|
+
yml_key = key.gsub( "-", "_" )
|
20
|
+
result[ yml_key ] = value
|
21
|
+
result
|
22
|
+
}
|
23
|
+
end
|
24
|
+
|
25
|
+
def unwind_keys( hash, separator = "." )
|
26
|
+
result = {}
|
27
|
+
hash.each do |key, value|
|
28
|
+
keys = key.to_s.split( separator )
|
29
|
+
curr = result
|
30
|
+
curr = curr[ keys.shift ] ||= {} while keys.size > 1
|
31
|
+
curr[ keys.shift ] = value
|
32
|
+
end
|
33
|
+
|
34
|
+
return result
|
35
|
+
end
|
36
|
+
|
37
|
+
def wind_keys( hash, separator = nil, subtree = false, prev_key = nil, result = {}, orig_hash = hash )
|
38
|
+
separator ||= I18n.default_separator
|
39
|
+
|
40
|
+
hash.each_pair do |key, value|
|
41
|
+
key = escape_default_separator( key, separator )
|
42
|
+
curr_key = [ prev_key, key ].compact.join( separator ).to_sym
|
43
|
+
|
44
|
+
if value.is_a?( Hash )
|
45
|
+
result[ curr_key ] = value if subtree
|
46
|
+
wind_keys( value, separator, subtree, curr_key, result, orig_hash )
|
47
|
+
else
|
48
|
+
result[ unescape_default_separator( curr_key ) ] = value
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
return result
|
53
|
+
end
|
54
|
+
|
55
|
+
def keys_to_yaml( keys )
|
56
|
+
# Using ya2yaml, if available, for UTF8 support
|
57
|
+
keys.respond_to?( :ya2yaml ) ? keys.ya2yaml( :escape_as_utf8 => true ) : keys.to_yaml
|
58
|
+
end
|
59
|
+
|
60
|
+
def keys_to_xml( keys )
|
61
|
+
raise "to_xml is broken" unless keys.respond_to?( :to_xml )
|
62
|
+
keys.to_xml
|
63
|
+
end
|
64
|
+
|
65
|
+
def convert_yml_to_xml( file = nil )
|
66
|
+
xml_root = 'hash'
|
67
|
+
yml = YAML.load_file( file )
|
68
|
+
wound_keys = wind_keys( yml )
|
69
|
+
|
70
|
+
out_dir = File.dirname( file )
|
71
|
+
out_file = "#{out_dir}/#{File.basename( file, '.yml' ) + '.xml'}"
|
72
|
+
File.open( out_file, 'w' ) { |f| f.puts( ( keys_to_xml( wound_keys ) ) ) }
|
73
|
+
end
|
74
|
+
|
75
|
+
def unescape_default_separator( key, separator = nil )
|
76
|
+
key.to_s.tr( SEPARATOR_ESCAPE_CHAR, separator || I18n.default_separator ).to_sym
|
77
|
+
end
|
78
|
+
|
79
|
+
def escape_default_separator( key, separator = nil )
|
80
|
+
key.to_s.tr( separator || I18n.default_separator, SEPARATOR_ESCAPE_CHAR )
|
81
|
+
end
|
82
|
+
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
@@ -3,20 +3,22 @@ module Vocab
|
|
3
3
|
class Rails < Base
|
4
4
|
DIFF = 'en.yml'
|
5
5
|
FULL = 'en.full.yml'
|
6
|
+
DIFF_SUFFIX = 'diff.yml'
|
7
|
+
FULL_SUFFIX = 'full.yml'
|
6
8
|
|
7
9
|
class << self
|
8
|
-
def write_diff( strings, plurals, path )
|
9
|
-
path ||= "#{Vocab.root}/#{
|
10
|
+
def write_diff( strings, plurals, path, locale = :en )
|
11
|
+
path ||= "#{Vocab.root}/#{locale}.#{DIFF_SUFFIX}"
|
10
12
|
write( strings, path )
|
11
13
|
end
|
12
14
|
|
13
|
-
def write_full( strings, plurals, path )
|
14
|
-
path ||= "#{Vocab.root}/#{
|
15
|
+
def write_full( strings, plurals, path, locale = :en )
|
16
|
+
path ||= "#{Vocab.root}/#{locale}.#{FULL_SUFFIX}"
|
15
17
|
write( strings, path )
|
16
18
|
end
|
17
19
|
|
18
|
-
def write( translations, path )
|
19
|
-
data = hasherize( translations ).to_yaml
|
20
|
+
def write( translations, path, locale = :en )
|
21
|
+
data = hasherize( translations, locale ).to_yaml
|
20
22
|
File.open( path, "w+" ) { |f| f.write( data ) }
|
21
23
|
Vocab.ui.say( "Extracted to #{path}" )
|
22
24
|
end
|
@@ -56,16 +58,30 @@ module Vocab
|
|
56
58
|
return {}
|
57
59
|
end
|
58
60
|
|
59
|
-
def
|
61
|
+
def extract_all( locales_root = nil, result_dir = nil )
|
62
|
+
locales_root ||= "#{Vocab.root}/config/locales"
|
63
|
+
result_dir ||= Vocab.root
|
64
|
+
|
60
65
|
translator = Vocab::Translator::Rails.new
|
66
|
+
translator.load_dir( locales_root )
|
67
|
+
|
68
|
+
translator.available_locales.each do |locale|
|
69
|
+
strings = translations( locales_root, locale )
|
70
|
+
path = "#{result_dir}/#{locale}.full.yml"
|
71
|
+
write( strings, path, locale )
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def translations( dir, locale = :en )
|
76
|
+
translator = Vocab::Translator::Rails.new( locale )
|
61
77
|
translator.load_dir( dir )
|
62
78
|
return translator.flattened_translations( :prefix => true )
|
63
79
|
end
|
64
80
|
|
65
|
-
def hasherize( diff )
|
66
|
-
translator = Vocab::Translator::Rails.new
|
81
|
+
def hasherize( diff, locale = :en )
|
82
|
+
translator = Vocab::Translator::Rails.new( locale )
|
67
83
|
diff.each do |key, value|
|
68
|
-
key = key.to_s.gsub!(
|
84
|
+
key = key.to_s.gsub!( /^#{locale.to_s}\./, '' )
|
69
85
|
translator.store( key, value )
|
70
86
|
end
|
71
87
|
return translator.translations( :prefix => true )
|
data/lib/vocab/merger/android.rb
CHANGED
@@ -1,10 +1,17 @@
|
|
1
1
|
module Vocab
|
2
2
|
module Merger
|
3
3
|
class Android < Base
|
4
|
+
FORMAT_PATTERN = /%(.+?)\b/
|
5
|
+
ARG_PATTERN = /\$(.+?)\b/
|
4
6
|
|
5
7
|
def initialize( locales_dir = nil, updates_dir = nil )
|
6
8
|
@locales_dir = locales_dir || 'res'
|
7
9
|
@updates_dir = updates_dir || 'tmp/translations'
|
10
|
+
@english_path = "#{@locales_dir}/values/strings.xml"
|
11
|
+
if File.exists?( @english_path )
|
12
|
+
@english_strings = english_strings
|
13
|
+
@english_plurals = english_plurals
|
14
|
+
end
|
8
15
|
end
|
9
16
|
|
10
17
|
def merge_file( path )
|
@@ -17,17 +24,17 @@ module Vocab
|
|
17
24
|
keys = string_keys
|
18
25
|
current = current_strings_for_locale( path )
|
19
26
|
updates = update_strings_for_locale( path )
|
20
|
-
return translation_hash( keys, current, updates, path )
|
27
|
+
return translation_hash( keys, current, updates, path, :string_format_changed? )
|
21
28
|
end
|
22
29
|
|
23
30
|
def plurals( path )
|
24
31
|
keys = plural_keys
|
25
32
|
current = current_plurals_for_locale( path )
|
26
33
|
updates = update_plurals_for_locale( path )
|
27
|
-
return translation_hash( keys, current, updates, path )
|
34
|
+
return translation_hash( keys, current, updates, path, :plural_format_changed? )
|
28
35
|
end
|
29
36
|
|
30
|
-
def translation_hash( keys, current, updates, path )
|
37
|
+
def translation_hash( keys, current, updates, path, format_checker = :string_format_changed? )
|
31
38
|
translation = {}
|
32
39
|
keys.each do |key|
|
33
40
|
next if Vocab::Translator::Base.ignore_key?( key )
|
@@ -35,6 +42,7 @@ module Vocab
|
|
35
42
|
value = updates[ key ] || current[ key ]
|
36
43
|
if value
|
37
44
|
translation[ key ] = value
|
45
|
+
check_matching_format_strings( key, value, path, format_checker )
|
38
46
|
else
|
39
47
|
Vocab.ui.warn( "No translation found for key #{key} while merging #{path}" )
|
40
48
|
end
|
@@ -43,6 +51,34 @@ module Vocab
|
|
43
51
|
return translation
|
44
52
|
end
|
45
53
|
|
54
|
+
def english_strings
|
55
|
+
return Vocab::Translator::Android.hash_from_xml( @english_path )
|
56
|
+
end
|
57
|
+
|
58
|
+
def english_plurals
|
59
|
+
return Vocab::Translator::Android.plurals_from_xml( @english_path )
|
60
|
+
end
|
61
|
+
|
62
|
+
def check_matching_format_strings( key, new_value, path, format_checker )
|
63
|
+
send( format_checker, key, new_value, path)
|
64
|
+
end
|
65
|
+
|
66
|
+
def plural_format_changed?( key, new_value, path )
|
67
|
+
new_value.each do |inner_key,inner_value|
|
68
|
+
if ( @english_plurals[ key ][ inner_key ].to_s.scan( FORMAT_PATTERN ) != inner_value.to_s.scan( FORMAT_PATTERN ) ) ||
|
69
|
+
( @english_plurals[ key ][ inner_key ].to_s.scan( ARG_PATTERN ) != inner_value.to_s.scan( ARG_PATTERN ) )
|
70
|
+
Vocab.ui.warn( "Format string mismatch for key #{key}, quantity #{inner_key} while merging #{path}. \n English: #{@english_plurals[ key ][ inner_key ]} \n Translation: #{new_value[ inner_key ]}" )
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def string_format_changed?( key, new_value, path )
|
76
|
+
if ( @english_strings[ key ].to_s.scan( FORMAT_PATTERN ) != new_value.to_s.scan( FORMAT_PATTERN ) ) ||
|
77
|
+
( @english_strings[ key ].to_s.scan( ARG_PATTERN ) != new_value.to_s.scan( ARG_PATTERN ) )
|
78
|
+
Vocab.ui.warn( "Format string mismatch for key #{key} while merging #{path}. \n English: #{@english_strings[ key ]} \n Translation: #{new_value}" )
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
46
82
|
def string_keys
|
47
83
|
return Vocab::Translator::Android.string_keys( @locales_dir )
|
48
84
|
end
|
data/lib/vocab/merger/rails.rb
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
module Vocab
|
2
2
|
module Merger
|
3
3
|
class Rails < Base
|
4
|
+
INTERPOLATION_PATTERN = /%{(.+?)}/
|
4
5
|
|
5
6
|
def initialize( locales_dir = nil, updates_dir = nil )
|
6
7
|
@locales_dir = locales_dir || 'config/locales'
|
@@ -13,6 +14,8 @@ module Vocab
|
|
13
14
|
|
14
15
|
# list of keys that need to be in the translated file
|
15
16
|
keys = Vocab::Merger::Rails.keys_for_file( locales_path )
|
17
|
+
english = Vocab::Merger::Rails.load_english( locales_path )
|
18
|
+
|
16
19
|
|
17
20
|
# existing translations already in the file
|
18
21
|
locales_translator = translator( locales_path )
|
@@ -27,6 +30,7 @@ module Vocab
|
|
27
30
|
updates_translator = translator( update_path )
|
28
31
|
updates = updates_translator.flattened_translations
|
29
32
|
|
33
|
+
|
30
34
|
# apply updated keys to locales hash
|
31
35
|
keys.each do |key|
|
32
36
|
next if Vocab::Translator::Base.ignore_key?( key )
|
@@ -34,6 +38,7 @@ module Vocab
|
|
34
38
|
value = updates[ key ] || locales[ key ]
|
35
39
|
if value
|
36
40
|
locales_translator.store( key, value )
|
41
|
+
check_matching_interpolations( key, english[ key ], value, locales_path )
|
37
42
|
else
|
38
43
|
Vocab.ui.warn( "No translation found for key #{key} while merging #{locales_path}" )
|
39
44
|
end
|
@@ -42,6 +47,12 @@ module Vocab
|
|
42
47
|
locales_translator.write_file( locales_path )
|
43
48
|
end
|
44
49
|
|
50
|
+
def check_matching_interpolations( key, old_value, new_value, locales_path )
|
51
|
+
if old_value.to_s.scan( INTERPOLATION_PATTERN ) != new_value.to_s.scan( INTERPOLATION_PATTERN )
|
52
|
+
Vocab.ui.warn( "Interpolation mismatch for key #{key} while merging #{locales_path}. \n English: #{old_value} Translation: #{new_value}" )
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
45
56
|
def self.keys_for_file( path )
|
46
57
|
en_path = Vocab::Translator::Rails.en_equivalent_path( path )
|
47
58
|
translator = Vocab::Translator::Rails.new
|
@@ -49,6 +60,13 @@ module Vocab
|
|
49
60
|
return translator.flattened_translations.keys
|
50
61
|
end
|
51
62
|
|
63
|
+
def self.load_english( path )
|
64
|
+
en_path = Vocab::Translator::Rails.en_equivalent_path( path )
|
65
|
+
translator = Vocab::Translator::Rails.new
|
66
|
+
translator.load_file( en_path )
|
67
|
+
return translator.flattened_translations
|
68
|
+
end
|
69
|
+
|
52
70
|
def translatable?( path )
|
53
71
|
if File.basename( path ) == 'en.yml'
|
54
72
|
Vocab.ui.warn( "can't translate english file #{path}" )
|
data/lib/vocab/version.rb
CHANGED
data/lib/vocab.rb
CHANGED
@@ -2,10 +2,19 @@ require 'fileutils'
|
|
2
2
|
require 'i18n'
|
3
3
|
require 'pathname'
|
4
4
|
require 'nokogiri'
|
5
|
+
require 'htmlentities'
|
6
|
+
require 'yaml'
|
7
|
+
require 'ya2yaml'
|
8
|
+
require 'active_support/core_ext'
|
9
|
+
require 'builder'
|
10
|
+
|
11
|
+
|
5
12
|
|
6
13
|
require 'vocab/application'
|
7
14
|
require 'vocab/extractor'
|
8
15
|
require 'vocab/translator'
|
16
|
+
require 'vocab/cleaner'
|
17
|
+
require 'vocab/converter'
|
9
18
|
require 'vocab/merger'
|
10
19
|
require 'vocab/settings'
|
11
20
|
require 'vocab/ui'
|
File without changes
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe "Vocab::Cleaner::Base" do
|
4
|
+
|
5
|
+
describe 'clean' do
|
6
|
+
|
7
|
+
it 'cleans a list of files' do
|
8
|
+
files = [ 'es.yml', 'en.yml' ]
|
9
|
+
Vocab::Cleaner::Base.should_receive( :files_to_clean ).and_return( files )
|
10
|
+
|
11
|
+
files.each do |file|
|
12
|
+
Vocab::Cleaner::Base.should_receive( :clean_file ).with( file )
|
13
|
+
Vocab.ui.should_receive( :say ).with( "Cleaning file: #{file}" )
|
14
|
+
end
|
15
|
+
Vocab::Cleaner::Base.clean
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
19
|
+
end
|