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,55 @@
|
|
1
|
+
module I18n
|
2
|
+
module Index
|
3
|
+
class Files < Hash
|
4
|
+
def [](filename)
|
5
|
+
fetch(filename)
|
6
|
+
rescue
|
7
|
+
store(filename, File.new(filename))
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
class File
|
12
|
+
attr_accessor :filename
|
13
|
+
|
14
|
+
def initialize(filename)
|
15
|
+
self.filename = filename
|
16
|
+
end
|
17
|
+
|
18
|
+
def source
|
19
|
+
@source ||= begin
|
20
|
+
source = ::File.read(filename) # TODO srsly ... how am i supposed to open a file in ruby 1.9?
|
21
|
+
source = ::File.open(filename, 'r:iso-8859-1:utf-8') { |f| f.read } unless source.valid_encoding?
|
22
|
+
source
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def ruby
|
27
|
+
@ruby ||= begin
|
28
|
+
parser.parse
|
29
|
+
rescue Ripper::RubyBuilder::ParseError => e
|
30
|
+
puts "\nWARNING Ruby 1.9 incompatible syntax in: " + e.message.gsub(Dir.pwd, '') + ". Can not index file."
|
31
|
+
Ruby::Program.new(source, filename)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def calls
|
36
|
+
@calls ||= ruby.select_translate_calls
|
37
|
+
end
|
38
|
+
|
39
|
+
def update(source)
|
40
|
+
@source = source
|
41
|
+
save
|
42
|
+
end
|
43
|
+
|
44
|
+
def save
|
45
|
+
::File.open(filename, 'w+') { |f| f.write(source) } # TODO need to modify/write unfiltered source for ERB
|
46
|
+
end
|
47
|
+
|
48
|
+
protected
|
49
|
+
|
50
|
+
def parser
|
51
|
+
@parser ||= Index.parser.new(source, filename)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
# wicked. but i can't see a better pattern right now. i want a module to include
|
2
|
+
# into the meta_class. and i don't want to set the io object globally (or on
|
3
|
+
# any class or module var) so i need to instantiate something, too.
|
4
|
+
|
5
|
+
module I18n
|
6
|
+
module Index
|
7
|
+
module Format
|
8
|
+
class Base
|
9
|
+
attr_accessor :io
|
10
|
+
|
11
|
+
def initialize(io = nil)
|
12
|
+
self.io = io || $stdout
|
13
|
+
end
|
14
|
+
|
15
|
+
def out(str)
|
16
|
+
io.print(str)
|
17
|
+
end
|
18
|
+
|
19
|
+
def setup(target)
|
20
|
+
$stdout.sync = true
|
21
|
+
(class << target; self; end).send(:include, self.class::Hooks)
|
22
|
+
target.format = self
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
class Stdout < Base
|
27
|
+
module Hooks
|
28
|
+
attr_accessor :format
|
29
|
+
|
30
|
+
def build(*args)
|
31
|
+
format.out "indexing files ... "
|
32
|
+
super
|
33
|
+
format.out "found #{occurences.size} occurences of #{keys.size} keys in #{filenames.size} files in total\n"
|
34
|
+
end
|
35
|
+
|
36
|
+
def save
|
37
|
+
format.out "saving index\n"
|
38
|
+
super
|
39
|
+
end
|
40
|
+
|
41
|
+
def parse(file)
|
42
|
+
format.out '.'
|
43
|
+
super
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
module I18n
|
2
|
+
module Index
|
3
|
+
class Key
|
4
|
+
class << self
|
5
|
+
def patterns(*keys)
|
6
|
+
keys.inject({}) { |result, key| result[key] = pattern(key); result }
|
7
|
+
end
|
8
|
+
|
9
|
+
def pattern(key)
|
10
|
+
key = key.to_s.dup
|
11
|
+
match_start = key.gsub!(/^\*/, '') ? '' : '^'
|
12
|
+
match_end = key.gsub!(/\*$/, '') ? '' : '$'
|
13
|
+
pattern = Regexp.escape("#{key}")
|
14
|
+
/#{match_start}#{pattern}#{match_end}/
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
attr_accessor :key
|
19
|
+
|
20
|
+
def initialize(key)
|
21
|
+
self.key = key
|
22
|
+
end
|
23
|
+
|
24
|
+
def matches?(*keys)
|
25
|
+
patterns = Key.patterns(*keys)
|
26
|
+
patterns.empty? || patterns.any? do |key, pattern|
|
27
|
+
self.key == key.to_sym || self.key.to_s =~ pattern
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def <=>(other)
|
32
|
+
key <=> other.key
|
33
|
+
end
|
34
|
+
|
35
|
+
def ==(other)
|
36
|
+
key == (other.respond_to?(:key) ? other.key : other)
|
37
|
+
end
|
38
|
+
alias eql? ==
|
39
|
+
|
40
|
+
def hash
|
41
|
+
key.hash
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module I18n
|
2
|
+
module Index
|
3
|
+
class Occurence
|
4
|
+
attr_reader :key, :filename, :position
|
5
|
+
|
6
|
+
def initialize(key, filename, position)
|
7
|
+
@key = key
|
8
|
+
@filename = filename
|
9
|
+
@position = position
|
10
|
+
end
|
11
|
+
|
12
|
+
def ==(other)
|
13
|
+
key == other.key && filename == other.filename && position == other.position
|
14
|
+
end
|
15
|
+
alias eql? ==
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
require 'i18n/index/base'
|
2
|
+
require 'i18n/index/key'
|
3
|
+
require 'i18n/index/occurence'
|
4
|
+
require 'i18n/index/format'
|
5
|
+
require 'i18n/index/simple/storage'
|
6
|
+
require 'i18n/index/simple/data'
|
7
|
+
require 'erb/stripper'
|
8
|
+
|
9
|
+
module I18n
|
10
|
+
module Index
|
11
|
+
class Simple < Base
|
12
|
+
include Storage
|
13
|
+
|
14
|
+
def find_call(*keys)
|
15
|
+
return unless key = data.keys.detect { |key, data| key.matches?(*keys) }
|
16
|
+
occurence = data.occurences(key).first
|
17
|
+
files[occurence.filename].ruby.select_translate_calls(:position => occurence.position).first
|
18
|
+
end
|
19
|
+
|
20
|
+
def find_calls(*keys)
|
21
|
+
keys = data.keys.select { |key, data| key.matches?(*keys) }
|
22
|
+
occurences = keys.map { |key| data.occurences(key) }.flatten
|
23
|
+
occurences.map do |occurence|
|
24
|
+
files[occurence.filename].ruby.select_translate_calls(:position => occurence.position)
|
25
|
+
end.flatten
|
26
|
+
end
|
27
|
+
|
28
|
+
def replace_key(call, search, replacement)
|
29
|
+
data.remove(call)
|
30
|
+
call.replace_key(search.to_s.gsub(/[^\w\.]/, ''), replacement.to_sym)
|
31
|
+
files[call.filename].update(call.root.src)
|
32
|
+
data.add(call)
|
33
|
+
save if built?
|
34
|
+
end
|
35
|
+
|
36
|
+
def data
|
37
|
+
@data ||= Data.new
|
38
|
+
build unless built?
|
39
|
+
@data
|
40
|
+
end
|
41
|
+
|
42
|
+
def keys
|
43
|
+
data.keys
|
44
|
+
end
|
45
|
+
|
46
|
+
def occurences
|
47
|
+
data.values.map { |value| value[:occurences] }.flatten
|
48
|
+
end
|
49
|
+
|
50
|
+
protected
|
51
|
+
|
52
|
+
def reset!
|
53
|
+
@built = false
|
54
|
+
@data = nil
|
55
|
+
end
|
56
|
+
|
57
|
+
def built?
|
58
|
+
@built
|
59
|
+
end
|
60
|
+
|
61
|
+
def build
|
62
|
+
reset!
|
63
|
+
@built = true
|
64
|
+
calls = filenames.map { |filename| files[filename].calls }.flatten
|
65
|
+
calls.each { |call| data.add(call) }
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module I18n
|
2
|
+
module Index
|
3
|
+
class Simple < Base
|
4
|
+
class Data < Hash
|
5
|
+
def add(call)
|
6
|
+
key = Key.new(call.full_key(true))
|
7
|
+
occurences(key) << Occurence.new(key, call.filename, call.position)
|
8
|
+
end
|
9
|
+
|
10
|
+
def remove(call)
|
11
|
+
key = Key.new(call.full_key(true))
|
12
|
+
occurences = occurences(key)
|
13
|
+
occurences.delete(Occurence.new(key, call.filename, call.position))
|
14
|
+
self.delete(key) if occurences.empty?
|
15
|
+
end
|
16
|
+
|
17
|
+
def occurences(key)
|
18
|
+
self[key] ||= { :occurences => [] }
|
19
|
+
self[key][:occurences]
|
20
|
+
end
|
21
|
+
|
22
|
+
def [](key)
|
23
|
+
key = Key.new(key) unless key.is_a?(Key)
|
24
|
+
super
|
25
|
+
end
|
26
|
+
|
27
|
+
def []=(key, value)
|
28
|
+
key = Key.new(key) unless key.is_a?(Key)
|
29
|
+
super
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,79 @@
|
|
1
|
+
module I18n
|
2
|
+
module Index
|
3
|
+
class Simple < Base
|
4
|
+
module Storage
|
5
|
+
class << self
|
6
|
+
def included(target)
|
7
|
+
target.send(:extend, ClassMethods)
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
module ClassMethods
|
12
|
+
def load_or_create(options = {})
|
13
|
+
exists?(options) ? load(options) : create(options)
|
14
|
+
end
|
15
|
+
|
16
|
+
protected
|
17
|
+
|
18
|
+
def create(options = {})
|
19
|
+
index = Simple.new(options)
|
20
|
+
index.update
|
21
|
+
index
|
22
|
+
end
|
23
|
+
|
24
|
+
def load(options)
|
25
|
+
::File.open(filename(options), 'r') { |f| ::Marshal.load(f) }
|
26
|
+
end
|
27
|
+
|
28
|
+
def exists?(options)
|
29
|
+
::File.exists?(filename(options))
|
30
|
+
end
|
31
|
+
|
32
|
+
def filename(options)
|
33
|
+
store_dir(options) + "/index.marshal"
|
34
|
+
end
|
35
|
+
|
36
|
+
def store_dir(options)
|
37
|
+
root_dir = options[:root_dir] || Dir.pwd
|
38
|
+
::File.expand_path(root_dir + '/.i18n')
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def exists?
|
44
|
+
::File.exists?(filename)
|
45
|
+
end
|
46
|
+
|
47
|
+
def update
|
48
|
+
reset!
|
49
|
+
build
|
50
|
+
save
|
51
|
+
end
|
52
|
+
|
53
|
+
def save
|
54
|
+
mkdir
|
55
|
+
::File.open(filename, 'w+') { |f| ::Marshal.dump(self, f) }
|
56
|
+
end
|
57
|
+
|
58
|
+
def delete
|
59
|
+
FileUtils.rm(filename) if exists? rescue Errno::ENOENT
|
60
|
+
end
|
61
|
+
|
62
|
+
def filename
|
63
|
+
self.class.send(:filename, :root_dir => root_dir)
|
64
|
+
end
|
65
|
+
|
66
|
+
def store_dir
|
67
|
+
self.class.send(:store_dir, :root_dir => root_dir)
|
68
|
+
end
|
69
|
+
|
70
|
+
def mkdir
|
71
|
+
FileUtils.mkdir_p(store_dir) unless ::File.exists?(store_dir)
|
72
|
+
end
|
73
|
+
|
74
|
+
def marshalled_vars
|
75
|
+
super + [:built, :data]
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
@@ -0,0 +1,89 @@
|
|
1
|
+
module Ruby
|
2
|
+
class ArgsList
|
3
|
+
def to_translate_args_list
|
4
|
+
meta_class.send(:include, TranslateArgsList)
|
5
|
+
self
|
6
|
+
end
|
7
|
+
end
|
8
|
+
|
9
|
+
module TranslateArgsList
|
10
|
+
def full_key(joined = false)
|
11
|
+
full_key = normalize_keys(scope, key)
|
12
|
+
joined ? join_key(full_key) : full_key
|
13
|
+
end
|
14
|
+
|
15
|
+
def key
|
16
|
+
first.arg.value if first
|
17
|
+
end
|
18
|
+
|
19
|
+
def scope
|
20
|
+
last.arg.is_a?(Ruby::Hash) ? last.arg.value[:scope] : nil
|
21
|
+
end
|
22
|
+
|
23
|
+
def key_matches?(keys)
|
24
|
+
keys = normalize_keys(keys)
|
25
|
+
keys == full_key[0, keys.length]
|
26
|
+
end
|
27
|
+
|
28
|
+
def replace_key(search, replace)
|
29
|
+
original_length = length
|
30
|
+
self.key, self.scope = compute_replace_keys(search, replace)
|
31
|
+
root.replace_src(row, column, original_length, to_ruby)
|
32
|
+
end
|
33
|
+
|
34
|
+
protected
|
35
|
+
|
36
|
+
def key=(key)
|
37
|
+
self[0] = key
|
38
|
+
end
|
39
|
+
|
40
|
+
def scope=(scope)
|
41
|
+
if scope
|
42
|
+
set_option(:scope, scope)
|
43
|
+
elsif options
|
44
|
+
options.delete(:scope)
|
45
|
+
pop if options.arg.empty?
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def compute_replace_keys(search, replace)
|
50
|
+
search = normalize_keys(search)
|
51
|
+
replace = normalize_keys(replace)
|
52
|
+
key = normalize_keys(self.key)
|
53
|
+
scope = normalize_keys(self.scope)
|
54
|
+
all = scope + key
|
55
|
+
|
56
|
+
all[key_index(search), search.length] = replace
|
57
|
+
|
58
|
+
if scope.empty?
|
59
|
+
key = all
|
60
|
+
else
|
61
|
+
key = all.slice!(-[key.length, all.size].min..-1) # i.e. we preserve the key length, this is debatable
|
62
|
+
scope = all.empty? ? nil : all
|
63
|
+
# scope = all.slice!(0, scope.length) # this would preserve the scope length
|
64
|
+
# key = all # comment this in, previous lines out and observe the tests
|
65
|
+
end
|
66
|
+
|
67
|
+
key = [scope.pop] if key.empty?
|
68
|
+
scope = scope.first if scope && scope.size == 1
|
69
|
+
scope = nil if scope && scope.empty?
|
70
|
+
|
71
|
+
[join_key(key), scope]
|
72
|
+
end
|
73
|
+
|
74
|
+
def key_index(search)
|
75
|
+
(all = full_key).each_index { |ix| return ix if all[ix, search.length] == search } and nil
|
76
|
+
end
|
77
|
+
|
78
|
+
def join_key(key)
|
79
|
+
key.map { |k| k.to_s }.join('.').to_sym
|
80
|
+
end
|
81
|
+
|
82
|
+
def normalize_keys(*args)
|
83
|
+
args.flatten.
|
84
|
+
map { |k| k.to_s.split(/\./) }.flatten.
|
85
|
+
reject { |k| k.empty? }.
|
86
|
+
map { |k| k.to_sym }
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|