i18n-tools 0.0.4 → 0.0.5
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/core_ext/hash/iterate_nested.rb +35 -0
- data/lib/core_ext/hash/slice.rb +20 -0
- data/lib/core_ext/hash/sorted_yaml_style.rb +17 -0
- data/lib/core_ext/hash/symbolize_keys.rb +14 -0
- data/lib/core_ext/module/attribute_accessors.rb +48 -0
- data/lib/core_ext/object/deep_clone.rb +5 -0
- data/lib/core_ext/object/instance_variables.rb +9 -0
- data/lib/core_ext/object/meta_class.rb +5 -0
- data/lib/core_ext/object/tap.rb +6 -0
- data/lib/i18n/backend/simple_storage.rb +119 -0
- data/lib/i18n/commands/keys.rb +84 -0
- data/lib/i18n/exceptions/key_exists.rb +9 -0
- data/lib/i18n/index.rb +33 -0
- data/lib/i18n/index/base.rb +38 -0
- data/lib/i18n/index/file.rb +55 -0
- data/lib/i18n/index/format.rb +49 -0
- data/lib/i18n/index/key.rb +45 -0
- data/lib/i18n/index/occurence.rb +18 -0
- data/lib/i18n/index/simple.rb +69 -0
- data/lib/i18n/index/simple/data.rb +34 -0
- data/lib/i18n/index/simple/storage.rb +79 -0
- data/lib/i18n/ripper2ruby.rb +7 -0
- data/lib/i18n/ripper2ruby/translate_args_list.rb +89 -0
- data/lib/i18n/ripper2ruby/translate_call.rb +66 -0
- data/lib/i18n/translation_properties.rb +38 -0
- data/test/all.rb +1 -1
- data/test/core_ext/hash_iterate_nested.rb +31 -0
- data/test/fixtures/all.rb.src +106 -0
- data/test/fixtures/config.yml +3 -0
- data/test/fixtures/locale/de.yml +4 -0
- data/test/fixtures/locale/en.yml +4 -0
- data/test/fixtures/source_1.rb +2 -1
- data/test/fixtures/translate/double_key.rb +32 -0
- data/test/fixtures/translate/double_scope.rb +32 -0
- data/test/fixtures/translate/single_key.rb +10 -0
- data/test/fixtures/translate/single_scope.rb +32 -0
- data/test/i18n/backend/simple_storage_test.rb +81 -0
- data/test/i18n/backend/translation_properties_test.rb +33 -0
- data/test/i18n/index/all.rb +1 -0
- data/test/i18n/index/args_replace_test.rb +218 -0
- data/test/i18n/index/calls_replace_test.rb +67 -0
- data/test/i18n/index/commands_test.rb +75 -0
- data/test/i18n/index/key_test.rb +32 -0
- data/test/i18n/index/simple_test.rb +67 -0
- data/test/i18n/ripper2ruby/translate_call_test.rb +98 -0
- data/test/test_helper.rb +66 -9
- metadata +49 -32
- data/MIT-LICENSE +0 -20
- data/README.textile +0 -1
- data/bin/i18n-keys +0 -6
- data/lib/ansi.rb +0 -19
- data/lib/i18n/keys.rb +0 -51
- data/lib/i18n/keys/commands.rb +0 -53
- data/lib/i18n/keys/formatter.rb +0 -39
- data/lib/i18n/keys/index.rb +0 -209
- data/lib/i18n/keys/occurence.rb +0 -120
- data/lib/i18n/parser/erb_parser.rb +0 -54
- data/lib/i18n/parser/ruby_parser.rb +0 -93
- data/test/commands_test.rb +0 -1
- data/test/erb_parser_test.rb +0 -31
- data/test/index_test.rb +0 -135
- data/test/keys_test.rb +0 -75
- data/test/occurence_test.rb +0 -130
- data/test/ruby_parser_test.rb +0 -54
@@ -0,0 +1,35 @@
|
|
1
|
+
class Hash
|
2
|
+
def each_nested(data = nil, keys = [], &block)
|
3
|
+
(data || self).each do |key, value|
|
4
|
+
case value
|
5
|
+
when Hash
|
6
|
+
each_nested(value, keys + [key], &block)
|
7
|
+
else
|
8
|
+
yield(keys + [key], value)
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
def select_nested(data = self, keys = [], &block)
|
14
|
+
case data
|
15
|
+
when Hash
|
16
|
+
data.inject({}) do |result, (key, value)|
|
17
|
+
value = select_nested(value, keys + [key], &block)
|
18
|
+
result[key] = value unless value.nil? || value.empty?
|
19
|
+
result
|
20
|
+
end
|
21
|
+
else
|
22
|
+
data if yield(keys, data)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def delete_nested_if(data = self, keys = [], &block)
|
27
|
+
data.each do |key, value|
|
28
|
+
if yield(keys + [key], value)
|
29
|
+
data.delete(key)
|
30
|
+
else
|
31
|
+
delete_nested_if(value, keys + [key], &block)
|
32
|
+
end
|
33
|
+
end if data.is_a?(Hash)
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# from activesupport
|
2
|
+
|
3
|
+
class Hash
|
4
|
+
# Returns a new hash with only the given keys.
|
5
|
+
def slice(*keys)
|
6
|
+
hash = self.class.new
|
7
|
+
keys.each { |k| hash[k] = self[k] if has_key?(k) }
|
8
|
+
hash
|
9
|
+
end
|
10
|
+
|
11
|
+
# Replaces the hash with only the given keys.
|
12
|
+
# Returns a hash contained the removed key/value pairs
|
13
|
+
# {:a => 1, :b => 2, :c => 3, :d => 4}.slice!(:a, :b) # => {:c => 3, :d =>4}
|
14
|
+
def slice!(*keys)
|
15
|
+
omit = slice(*self.keys - keys)
|
16
|
+
hash = slice(*keys)
|
17
|
+
replace(hash)
|
18
|
+
omit
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
class Hash
|
2
|
+
attr_accessor :to_yaml_style
|
3
|
+
|
4
|
+
def set_yaml_style(style)
|
5
|
+
self.to_yaml_style = style
|
6
|
+
each { |key, value| value.set_yaml_style(style) if value.is_a?(Hash) }
|
7
|
+
end
|
8
|
+
|
9
|
+
def to_yaml(opts = {})
|
10
|
+
YAML::quick_emit(object_id, opts) do |out|
|
11
|
+
out.map(taguri, to_yaml_style) do |map|
|
12
|
+
elements = to_yaml_style == :sorted ? sort : self
|
13
|
+
elements.each { |k, v| map.add(k, v) }
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
class Hash
|
2
|
+
# Return a new hash with all keys converted to symbols.
|
3
|
+
def symbolize_keys
|
4
|
+
inject({}) do |options, (key, value)|
|
5
|
+
options[(key.to_sym rescue key) || key] = value
|
6
|
+
options
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
# Destructively convert all keys to symbols.
|
11
|
+
def symbolize_keys!
|
12
|
+
self.replace(self.symbolize_keys)
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
# from activesupport
|
2
|
+
|
3
|
+
class Module
|
4
|
+
def mattr_reader(*syms)
|
5
|
+
syms.each do |sym|
|
6
|
+
next if sym.is_a?(Hash)
|
7
|
+
class_eval(<<-EOS, __FILE__, __LINE__)
|
8
|
+
unless defined? @@#{sym} # unless defined? @@pagination_options
|
9
|
+
@@#{sym} = nil # @@pagination_options = nil
|
10
|
+
end # end
|
11
|
+
#
|
12
|
+
def self.#{sym} # def self.pagination_options
|
13
|
+
@@#{sym} # @@pagination_options
|
14
|
+
end # end
|
15
|
+
#
|
16
|
+
def #{sym} # def pagination_options
|
17
|
+
@@#{sym} # @@pagination_options
|
18
|
+
end # end
|
19
|
+
EOS
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def mattr_writer(*syms)
|
24
|
+
options = syms.last.is_a?(Hash) ? syms.pop : {}
|
25
|
+
syms.each do |sym|
|
26
|
+
class_eval(<<-EOS, __FILE__, __LINE__)
|
27
|
+
unless defined? @@#{sym} # unless defined? @@pagination_options
|
28
|
+
@@#{sym} = nil # @@pagination_options = nil
|
29
|
+
end # end
|
30
|
+
#
|
31
|
+
def self.#{sym}=(obj) # def self.pagination_options=(obj)
|
32
|
+
@@#{sym} = obj # @@pagination_options = obj
|
33
|
+
end # end
|
34
|
+
#
|
35
|
+
#{" #
|
36
|
+
def #{sym}=(obj) # def pagination_options=(obj)
|
37
|
+
@@#{sym} = obj # @@pagination_options = obj
|
38
|
+
end # end
|
39
|
+
" unless options[:instance_writer] == false } # # instance writer above is generated unless options[:instance_writer] == false
|
40
|
+
EOS
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def mattr_accessor(*syms)
|
45
|
+
mattr_reader(*syms)
|
46
|
+
mattr_writer(*syms)
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,9 @@
|
|
1
|
+
class Object
|
2
|
+
def instance_variables_get(*keys)
|
3
|
+
keys.inject({}) { |result, key| result[key] = instance_variable_get(:"@#{key}"); result }
|
4
|
+
end
|
5
|
+
|
6
|
+
def instance_variables_set(vars)
|
7
|
+
vars.each { |key, value| instance_variable_set(:"@#{key}", value) }
|
8
|
+
end
|
9
|
+
end
|
@@ -0,0 +1,119 @@
|
|
1
|
+
require 'i18n'
|
2
|
+
require 'i18n/exceptions/key_exists'
|
3
|
+
require 'i18n/translation_properties'
|
4
|
+
require 'core_ext/object/deep_clone'
|
5
|
+
require 'core_ext/hash/iterate_nested'
|
6
|
+
require 'core_ext/hash/sorted_yaml_style'
|
7
|
+
|
8
|
+
module I18n
|
9
|
+
module Backend
|
10
|
+
class SimpleStorage < Simple
|
11
|
+
@@sort_keys = true
|
12
|
+
|
13
|
+
class << self
|
14
|
+
def sort_keys
|
15
|
+
@@sort_keys
|
16
|
+
end
|
17
|
+
|
18
|
+
def sort_keys=(sort_keys)
|
19
|
+
@@sort_keys = sort_keys
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def interpolate(locale, original, values = {})
|
24
|
+
return original unless original.is_a?(String)
|
25
|
+
super.tap { |string| set_translation_properties(string, original.properties) }
|
26
|
+
end
|
27
|
+
|
28
|
+
def store_translations(locale, data)
|
29
|
+
data.each_nested { |key, value| raise KeyExists.new(locale, key) if lookup(locale, key) }
|
30
|
+
super
|
31
|
+
end
|
32
|
+
|
33
|
+
def copy_translations(from, to)
|
34
|
+
init_translations unless initialized?
|
35
|
+
I18n.available_locales.each do |locale|
|
36
|
+
store_translations(locale, to => I18n.t(from, :raise => true))
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def remove_translation(key)
|
41
|
+
init_translations unless initialized?
|
42
|
+
key = I18n.send(:normalize_translation_keys, nil, key, nil)
|
43
|
+
keys = available_locales.map { |locale| [locale] + key }
|
44
|
+
translations.delete_nested_if { |k, v| keys.include?(k) }
|
45
|
+
end
|
46
|
+
|
47
|
+
def save_translations(filenames = I18n.load_path.flatten)
|
48
|
+
Array(filenames).each do |filename|
|
49
|
+
save_file(filename, by_filename(filename))
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
protected
|
54
|
+
|
55
|
+
def load_yml(filename)
|
56
|
+
YAML::load(IO.read(filename)).tap do |data|
|
57
|
+
set_translation_properties(data, :filename => filename) if data
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def by_filename(filename)
|
62
|
+
select_translations { |keys, translation| translation.filename == filename }
|
63
|
+
end
|
64
|
+
|
65
|
+
def select_translations(&block)
|
66
|
+
init_translations unless initialized?
|
67
|
+
translations.select_nested(&block)
|
68
|
+
end
|
69
|
+
|
70
|
+
def each_translation(&block)
|
71
|
+
init_translations unless initialized?
|
72
|
+
translations.each_nested { |keys, t| block.call(keys.first, keys[1..-1], t) }
|
73
|
+
end
|
74
|
+
|
75
|
+
def save_file(filename, data)
|
76
|
+
type = File.extname(filename).tr('.', '').downcase
|
77
|
+
raise UnknownFileType.new(type, filename) unless respond_to?(:"save_#{type}")
|
78
|
+
send(:"save_#{type}", filename, data)
|
79
|
+
end
|
80
|
+
|
81
|
+
def save_yml(filename, data)
|
82
|
+
data = unset_translation_properties(data.deep_clone)
|
83
|
+
data = deep_stringify_keys(data)
|
84
|
+
File.open(filename, 'w+') do |f|
|
85
|
+
data.set_yaml_style(:sorted) if self.class.sort_keys
|
86
|
+
f.write(data.to_yaml)
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
def set_translation_properties(value, properties)
|
91
|
+
case value
|
92
|
+
when Hash
|
93
|
+
value.each_nested { |key, value| set_translation_properties(value, properties) }
|
94
|
+
else
|
95
|
+
value.meta_class.send(:include, TranslationProperties) unless value.respond_to?(:property_names)
|
96
|
+
value.set_properties(properties)
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
def unset_translation_properties(value)
|
101
|
+
case value
|
102
|
+
when Hash
|
103
|
+
value.each_nested { |key, value| value.unset_properties }
|
104
|
+
else
|
105
|
+
value.unset_properties
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
# Return a new hash with all keys and nested keys converted to strings.
|
110
|
+
def deep_stringify_keys(hash)
|
111
|
+
hash.inject({}) { |result, (key, value)|
|
112
|
+
value = deep_stringify_keys(value) if value.is_a?(Hash)
|
113
|
+
result[key.to_s] = value
|
114
|
+
result
|
115
|
+
}
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
@@ -0,0 +1,84 @@
|
|
1
|
+
require 'i18n/index'
|
2
|
+
require 'i18n/exceptions/key_exists'
|
3
|
+
require 'core_ext/hash/slice'
|
4
|
+
require 'highline/import'
|
5
|
+
require 'highlighters/ansi'
|
6
|
+
|
7
|
+
module I18n
|
8
|
+
module Commands
|
9
|
+
class Keys
|
10
|
+
def initialize(highlighter = Highlighters::Ansi.new(:bold), out = $stdout)
|
11
|
+
@highlighter = highlighter
|
12
|
+
@out = out
|
13
|
+
end
|
14
|
+
|
15
|
+
def find(keys, options = {})
|
16
|
+
index = index(options)
|
17
|
+
index.find_calls(*keys).each do |call|
|
18
|
+
log "\n" + call.to_s(:context => options[:context], :highlight => @highlighter).gsub(Dir.pwd, '')
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def replace(search, replace, options)
|
23
|
+
key = search.gsub(/^\*\.|\.\*$/, '')
|
24
|
+
interactive = options.has_key?(:'interactive') ? options[:'interactive'] : true
|
25
|
+
index = index(options)
|
26
|
+
found = false
|
27
|
+
|
28
|
+
index.find_calls(search).each do |call|
|
29
|
+
if replace?(call, replace, :interactive => interactive)
|
30
|
+
I18n.backend.copy_translations(key, replace)
|
31
|
+
index.replace_key(call, search, replace)
|
32
|
+
I18n.backend.remove_translation(key)
|
33
|
+
found = true
|
34
|
+
end
|
35
|
+
break if cancelled?
|
36
|
+
end
|
37
|
+
|
38
|
+
log "No occurences were found or no replacements made for: #{search}." unless found
|
39
|
+
rescue I18n::KeyExists => e
|
40
|
+
log e.message
|
41
|
+
end
|
42
|
+
|
43
|
+
protected
|
44
|
+
|
45
|
+
def log(str)
|
46
|
+
@out.puts(str)
|
47
|
+
end
|
48
|
+
|
49
|
+
def index(options) # TODO use context + verbose options
|
50
|
+
method = options.delete(:index) ? :load_or_create : :new
|
51
|
+
options = options.slice(:root_dir, :pattern, :format, :context)
|
52
|
+
options[:format] ||= I18n::Index::Format::Stdout.new(@out)
|
53
|
+
I18n::Index.send(method, options)
|
54
|
+
end
|
55
|
+
|
56
|
+
def cancelled?
|
57
|
+
@cancelled
|
58
|
+
end
|
59
|
+
|
60
|
+
def replace?(call, replace, options = { :interactive => true })
|
61
|
+
return true if @all || !options[:interactive]
|
62
|
+
return false if @cancelled
|
63
|
+
case answer = confirm_replace(call, replace)[0, 1]
|
64
|
+
when 'a'
|
65
|
+
@all = true
|
66
|
+
when 'c'
|
67
|
+
@cancelled = true and false
|
68
|
+
else
|
69
|
+
answer == 'y'
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def confirm_replace(call, replace)
|
74
|
+
log call.to_s
|
75
|
+
log call.context(:highlight => @highlighter)
|
76
|
+
msg = "Replace this occurence of the key \"#{call.key}\" with \"#{replace}\"? [Y]es [N]o [A]ll [C]ancel"
|
77
|
+
answer = ask(msg, %w(y yes n no a all c cancel)) do |q|
|
78
|
+
q.case = :downcase
|
79
|
+
q.readline = true
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
data/lib/i18n/index.rb
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
require 'core_ext/module/attribute_accessors'
|
2
|
+
|
3
|
+
require 'i18n/index/file'
|
4
|
+
require 'i18n/index/simple'
|
5
|
+
require 'i18n/ripper2ruby'
|
6
|
+
|
7
|
+
module I18n
|
8
|
+
module Index
|
9
|
+
mattr_accessor :pattern, :implementation, :parser, :filters
|
10
|
+
|
11
|
+
@@implementation = I18n::Index::Simple
|
12
|
+
@@parser = Ripper::RubyBuilder
|
13
|
+
@@filters = { '.erb' => lambda { |source| Erb::Stripper.new.to_ruby(source) } }
|
14
|
+
@@pattern = '**/*.{rb,erb}'
|
15
|
+
|
16
|
+
class << self
|
17
|
+
def new(*args)
|
18
|
+
implementation.new(*args)
|
19
|
+
end
|
20
|
+
|
21
|
+
def load_or_create(*args)
|
22
|
+
implementation.load_or_create(*args)
|
23
|
+
end
|
24
|
+
|
25
|
+
def filter(source, filename)
|
26
|
+
filters.each do |extname, filter|
|
27
|
+
source = filter.call(source) if ::File.extname(filename) == extname
|
28
|
+
end
|
29
|
+
source
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
require 'core_ext/object/instance_variables'
|
2
|
+
|
3
|
+
module I18n
|
4
|
+
module Index
|
5
|
+
class Base
|
6
|
+
attr_accessor :root_dir, :pattern
|
7
|
+
|
8
|
+
def initialize(options = {})
|
9
|
+
@root_dir = options[:root_dir] || Dir.pwd
|
10
|
+
@pattern = options[:pattern] || Index.pattern
|
11
|
+
options[:format].setup(self) if options[:format]
|
12
|
+
end
|
13
|
+
|
14
|
+
def filenames
|
15
|
+
Dir[root_dir + '/' + pattern]
|
16
|
+
end
|
17
|
+
|
18
|
+
def files
|
19
|
+
@files ||= Files.new
|
20
|
+
end
|
21
|
+
|
22
|
+
protected
|
23
|
+
|
24
|
+
def marshalled_vars
|
25
|
+
# TODO marshalling :files works but makes things much slower during tests - check this for real situations
|
26
|
+
[:root_dir, :pattern]
|
27
|
+
end
|
28
|
+
|
29
|
+
def marshal_dump
|
30
|
+
instance_variables_get(*marshalled_vars)
|
31
|
+
end
|
32
|
+
|
33
|
+
def marshal_load(data)
|
34
|
+
instance_variables_set(data.slice(*marshalled_vars))
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|