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.
Files changed (42) hide show
  1. data/lib/vocab/application.rb +24 -3
  2. data/lib/vocab/cleaner/android.rb +15 -0
  3. data/lib/vocab/cleaner/base.rb +14 -0
  4. data/lib/vocab/cleaner/rails.rb +141 -0
  5. data/lib/vocab/cleaner.rb +7 -0
  6. data/lib/vocab/converter/base.rb +17 -0
  7. data/lib/vocab/converter/rails.rb +86 -0
  8. data/lib/vocab/converter.rb +6 -0
  9. data/lib/vocab/extractor/rails.rb +26 -10
  10. data/lib/vocab/merger/android.rb +39 -3
  11. data/lib/vocab/merger/rails.rb +18 -0
  12. data/lib/vocab/translator/rails.rb +4 -0
  13. data/lib/vocab/version.rb +1 -1
  14. data/lib/vocab.rb +9 -0
  15. data/spec/cleaner/android_spec.rb +0 -0
  16. data/spec/cleaner/base_spec.rb +19 -0
  17. data/spec/cleaner/rails_spec.rb +79 -0
  18. data/spec/converter/base_spec.rb +23 -0
  19. data/spec/converter/rails_spec.rb +32 -0
  20. data/spec/data/android/locales/values/strings.xml +1 -1
  21. data/spec/data/android/locales/values-fr/strings.xml +21 -0
  22. data/spec/data/rails/full/cn.full.yml +0 -0
  23. data/spec/data/rails/full/en.expected.yml +4 -0
  24. data/spec/data/rails/full/en.full.yml +4 -0
  25. data/spec/data/rails/full/es.expected.yml +8 -0
  26. data/spec/data/rails/full/es.full.yml +8 -0
  27. data/spec/data/rails/full/fr.expected.yml +5 -0
  28. data/spec/data/rails/full/fr.full.yml +11 -0
  29. data/spec/data/rails/full/test.diff.yml +4 -0
  30. data/spec/data/rails/xml/in_file.xml +16 -0
  31. data/spec/data/rails/xml/in_file.yml +19 -0
  32. data/spec/data/rails/xml/out_file_expected.xml +16 -0
  33. data/spec/data/rails/xml/out_file_expected.yml +19 -0
  34. data/spec/data/rails/xml/test_file.xml +16 -0
  35. data/spec/data/rails/xml/test_file.yml +19 -0
  36. data/spec/extractor/android_spec.rb +2 -2
  37. data/spec/extractor/rails_spec.rb +15 -0
  38. data/spec/merger/android_spec.rb +13 -0
  39. data/spec/merger/rails_spec.rb +7 -0
  40. data/spec/validator/android_spec.rb +2 -2
  41. data/vocab.gemspec +4 -0
  42. metadata +142 -11
@@ -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] [file]'
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.path = commands[2]
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
- Extractor::Rails.extract
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,15 @@
1
+ module Vocab
2
+ module Cleaner
3
+ class Android < Base
4
+ class << self
5
+ def clean_file( file )
6
+ # TODO: implement
7
+ end
8
+
9
+ def files_to_clean
10
+ return Dir.glob( "*.full.xml" )
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,14 @@
1
+ module Vocab
2
+ module Cleaner
3
+ class Base
4
+ class << self
5
+ def clean
6
+ files_to_clean.each do |file|
7
+ Vocab.ui.say( "Cleaning file: #{file}" )
8
+ clean_file( file )
9
+ end
10
+ end
11
+ end
12
+ end
13
+ end
14
+ end
@@ -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. &gt; --> > )
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 == "&quot;" ? "\\\"" : 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,7 @@
1
+ module Vocab
2
+ module Cleaner
3
+ autoload :Base, 'vocab/cleaner/base'
4
+ autoload :Rails, 'vocab/cleaner/rails'
5
+ autoload :Android, 'vocab/cleaner/android'
6
+ end
7
+ 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
@@ -0,0 +1,6 @@
1
+ module Vocab
2
+ module Converter
3
+ autoload :Base, 'vocab/converter/base'
4
+ autoload :Rails, 'vocab/converter/rails'
5
+ end
6
+ 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}/#{DIFF}"
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}/#{FULL}"
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 translations( dir )
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!( /^en\./, '' )
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 )
@@ -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
@@ -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}" )
@@ -14,6 +14,10 @@ module Vocab
14
14
  @locale = locale
15
15
  end
16
16
 
17
+ def available_locales
18
+ return @backend.available_locales
19
+ end
20
+
17
21
  def load_dir( dir )
18
22
  I18n.load_path = Dir.glob( "#{dir}/**/*.{yml,rb}" )
19
23
  load_translations
data/lib/vocab/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Vocab
2
- VERSION = "0.0.6"
2
+ VERSION = "0.1.0"
3
3
  end
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