vocab 0.0.6 → 0.1.0
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/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
|