translate-rails3-plus 0.0.11 → 0.0.12
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.
- checksums.yaml +4 -4
- data/MIT-LICENSE +20 -20
- data/README.md +89 -89
- data/Rakefile +35 -35
- data/VERSION +1 -1
- data/app/controllers/translate_controller.rb +218 -218
- data/app/helpers/translate_helper.rb +53 -53
- data/app/views/layouts/translate.html.erb +495 -495
- data/app/views/translate/_array_form.html.erb +45 -45
- data/app/views/translate/_pagination.html.erb +24 -24
- data/app/views/translate/_string_form.html.erb +35 -35
- data/app/views/translate/index.html.erb +96 -96
- data/config/routes.rb +5 -5
- data/init.rb +1 -1
- data/lib/tasks/translate.rake +200 -200
- data/lib/translate.rb +17 -17
- data/lib/translate/file.rb +35 -35
- data/lib/translate/keys.rb +194 -194
- data/lib/translate/log.rb +35 -35
- data/lib/translate/routes.rb +11 -11
- data/lib/translate/storage.rb +28 -28
- data/spec/controllers/translate_controller_spec.rb +130 -130
- data/spec/file_spec.rb +54 -54
- data/spec/files/translate/app/models/article.rb +12 -12
- data/spec/files/translate/app/views/category.erb +1 -1
- data/spec/files/translate/app/views/category.html +1 -1
- data/spec/files/translate/app/views/category.html.erb +1 -1
- data/spec/files/translate/app/views/category.rhtml +5 -5
- data/spec/keys_spec.rb +179 -179
- data/spec/log_spec.rb +47 -47
- data/spec/spec_helper.rb +11 -11
- data/spec/storage_spec.rb +33 -33
- metadata +2 -2
data/lib/translate.rb
CHANGED
@@ -1,17 +1,17 @@
|
|
1
|
-
require 'ya2yaml'
|
2
|
-
|
3
|
-
module Translate
|
4
|
-
class Engine < Rails::Engine
|
5
|
-
end if defined?(Rails) && Rails::VERSION::MAJOR >= 3
|
6
|
-
|
7
|
-
class << self
|
8
|
-
# For configuring Google Translate API key
|
9
|
-
attr_accessor :api_key
|
10
|
-
# For configuring Bing Application id
|
11
|
-
attr_accessor :app_id
|
12
|
-
end
|
13
|
-
end
|
14
|
-
|
15
|
-
Dir[File.join(File.dirname(__FILE__), "translate", "*.rb")].each do |file|
|
16
|
-
require file
|
17
|
-
end
|
1
|
+
require 'ya2yaml'
|
2
|
+
|
3
|
+
module Translate
|
4
|
+
class Engine < Rails::Engine
|
5
|
+
end if defined?(Rails) && Rails::VERSION::MAJOR >= 3
|
6
|
+
|
7
|
+
class << self
|
8
|
+
# For configuring Google Translate API key
|
9
|
+
attr_accessor :api_key
|
10
|
+
# For configuring Bing Application id
|
11
|
+
attr_accessor :app_id
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
Dir[File.join(File.dirname(__FILE__), "translate", "*.rb")].each do |file|
|
16
|
+
require file
|
17
|
+
end
|
data/lib/translate/file.rb
CHANGED
@@ -1,35 +1,35 @@
|
|
1
|
-
require 'fileutils'
|
2
|
-
|
3
|
-
class Translate::File
|
4
|
-
attr_accessor :path
|
5
|
-
|
6
|
-
def initialize(path)
|
7
|
-
self.path = path
|
8
|
-
end
|
9
|
-
|
10
|
-
def write(keys)
|
11
|
-
FileUtils.mkdir_p File.dirname(path)
|
12
|
-
File.open(path, "w") do |file|
|
13
|
-
file.puts keys_to_yaml(Translate::File.deep_stringify_keys(keys))
|
14
|
-
end
|
15
|
-
end
|
16
|
-
|
17
|
-
def read
|
18
|
-
File.exists?(path) ? YAML::load(IO.read(path)) : {}
|
19
|
-
end
|
20
|
-
|
21
|
-
# Stringifying keys for prettier YAML
|
22
|
-
def self.deep_stringify_keys(hash)
|
23
|
-
hash.inject({}) { |result, (key, value)|
|
24
|
-
value = deep_stringify_keys(value) if value.is_a? Hash
|
25
|
-
result[(key.to_s rescue key) || key] = value
|
26
|
-
result
|
27
|
-
}
|
28
|
-
end
|
29
|
-
|
30
|
-
private
|
31
|
-
def keys_to_yaml(keys)
|
32
|
-
# Using ya2yaml, if available, for UTF8 support
|
33
|
-
keys.respond_to?(:ya2yaml) ? keys.ya2yaml(:escape_as_utf8 => true) : keys.to_yaml
|
34
|
-
end
|
35
|
-
end
|
1
|
+
require 'fileutils'
|
2
|
+
|
3
|
+
class Translate::File
|
4
|
+
attr_accessor :path
|
5
|
+
|
6
|
+
def initialize(path)
|
7
|
+
self.path = path
|
8
|
+
end
|
9
|
+
|
10
|
+
def write(keys)
|
11
|
+
FileUtils.mkdir_p File.dirname(path)
|
12
|
+
File.open(path, "w") do |file|
|
13
|
+
file.puts keys_to_yaml(Translate::File.deep_stringify_keys(keys))
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def read
|
18
|
+
File.exists?(path) ? YAML::load(IO.read(path)) : {}
|
19
|
+
end
|
20
|
+
|
21
|
+
# Stringifying keys for prettier YAML
|
22
|
+
def self.deep_stringify_keys(hash)
|
23
|
+
hash.inject({}) { |result, (key, value)|
|
24
|
+
value = deep_stringify_keys(value) if value.is_a? Hash
|
25
|
+
result[(key.to_s rescue key) || key] = value
|
26
|
+
result
|
27
|
+
}
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
def keys_to_yaml(keys)
|
32
|
+
# Using ya2yaml, if available, for UTF8 support
|
33
|
+
keys.respond_to?(:ya2yaml) ? keys.ya2yaml(:escape_as_utf8 => true) : keys.to_yaml
|
34
|
+
end
|
35
|
+
end
|
data/lib/translate/keys.rb
CHANGED
@@ -1,194 +1,194 @@
|
|
1
|
-
require 'pathname'
|
2
|
-
|
3
|
-
class Translate::Keys
|
4
|
-
# Allows keys extracted from lookups in files to be cached
|
5
|
-
def self.files
|
6
|
-
@@files ||= Translate::Keys.new.files
|
7
|
-
end
|
8
|
-
|
9
|
-
# Allows flushing of the files cache
|
10
|
-
def self.files=(files)
|
11
|
-
@@files = files
|
12
|
-
end
|
13
|
-
|
14
|
-
def files
|
15
|
-
@files ||= extract_files
|
16
|
-
end
|
17
|
-
alias_method :to_hash, :files
|
18
|
-
|
19
|
-
def keys
|
20
|
-
files.keys
|
21
|
-
end
|
22
|
-
alias_method :to_a, :keys
|
23
|
-
|
24
|
-
def i18n_keys(locale)
|
25
|
-
I18n.backend.send(:init_translations) unless I18n.backend.initialized?
|
26
|
-
Translate::Keys.to_shallow_hash(I18n.backend.send(:translations)[locale.to_sym]).keys.sort
|
27
|
-
end
|
28
|
-
|
29
|
-
def duplicate_keys
|
30
|
-
Translate::Keys.translated_locales.inject({}) do |missing, locale|
|
31
|
-
missing[locale] = i18n_keys(I18n.default_locale).map do |key|
|
32
|
-
I18n.backend.send(:lookup, locale, key) == I18n.backend.send(:lookup, I18n.default_locale, key) ? key : nil
|
33
|
-
end.compact
|
34
|
-
missing
|
35
|
-
end
|
36
|
-
end
|
37
|
-
|
38
|
-
def untranslated_keys
|
39
|
-
Translate::Keys.translated_locales.inject({}) do |missing, locale|
|
40
|
-
missing[locale] = i18n_keys(I18n.default_locale).map do |key|
|
41
|
-
I18n.backend.send(:lookup, locale, key).nil? ? key : nil
|
42
|
-
end.compact
|
43
|
-
missing
|
44
|
-
end
|
45
|
-
end
|
46
|
-
|
47
|
-
def missing_keys
|
48
|
-
locale = I18n.default_locale; yaml_keys = {}
|
49
|
-
yaml_keys = Translate::Storage.file_paths(locale).inject({}) do |keys, path|
|
50
|
-
keys = keys.deep_merge(Translate::File.new(path).read[locale.to_s])
|
51
|
-
end
|
52
|
-
files.reject { |key, file| self.class.contains_key?(yaml_keys, key) }
|
53
|
-
end
|
54
|
-
|
55
|
-
def self.translated_locales
|
56
|
-
I18n.available_locales.reject { |locale| [:root, I18n.default_locale.to_sym].include?(locale) }
|
57
|
-
end
|
58
|
-
|
59
|
-
# Checks if a nested hash contains the keys in dot separated I18n key.
|
60
|
-
#
|
61
|
-
# Example:
|
62
|
-
#
|
63
|
-
# hash = {
|
64
|
-
# :foo => {
|
65
|
-
# :bar => {
|
66
|
-
# :baz => 1
|
67
|
-
# }
|
68
|
-
# }
|
69
|
-
# }
|
70
|
-
#
|
71
|
-
# contains_key?("foo", key) # => true
|
72
|
-
# contains_key?("foo.bar", key) # => true
|
73
|
-
# contains_key?("foo.bar.baz", key) # => true
|
74
|
-
# contains_key?("foo.bar.baz.bla", key) # => false
|
75
|
-
#
|
76
|
-
def self.contains_key?(hash, key)
|
77
|
-
keys = key.to_s.split(".")
|
78
|
-
return false if keys.empty?
|
79
|
-
!keys.inject(HashWithIndifferentAccess.new(hash)) do |memo, key|
|
80
|
-
memo.is_a?(Hash) ? memo.try(:[], key) : nil
|
81
|
-
end.nil?
|
82
|
-
end
|
83
|
-
|
84
|
-
# Convert something like:
|
85
|
-
#
|
86
|
-
# {
|
87
|
-
# :pressrelease => {
|
88
|
-
# :label => {
|
89
|
-
# :one => "Pressmeddelande"
|
90
|
-
# }
|
91
|
-
# }
|
92
|
-
# }
|
93
|
-
#
|
94
|
-
# to:
|
95
|
-
#
|
96
|
-
# {'pressrelease.label.one' => "Pressmeddelande"}
|
97
|
-
#
|
98
|
-
def self.to_shallow_hash(hash)
|
99
|
-
hash.inject({}) do |shallow_hash, (key, value)|
|
100
|
-
if value.is_a?(Hash)
|
101
|
-
to_shallow_hash(value).each do |sub_key, sub_value|
|
102
|
-
shallow_hash[[key, sub_key].join(".")] = sub_value
|
103
|
-
end
|
104
|
-
else
|
105
|
-
shallow_hash[key.to_s] = value
|
106
|
-
end
|
107
|
-
shallow_hash
|
108
|
-
end
|
109
|
-
end
|
110
|
-
|
111
|
-
# Convert something like:
|
112
|
-
#
|
113
|
-
# {'pressrelease.label.one' => "Pressmeddelande"}
|
114
|
-
#
|
115
|
-
# to:
|
116
|
-
#
|
117
|
-
# {
|
118
|
-
# :pressrelease => {
|
119
|
-
# :label => {
|
120
|
-
# :one => "Pressmeddelande"
|
121
|
-
# }
|
122
|
-
# }
|
123
|
-
# }
|
124
|
-
def self.to_deep_hash(hash)
|
125
|
-
hash.inject({}) do |deep_hash, (key, value)|
|
126
|
-
keys = key.to_s.split('.').reverse
|
127
|
-
leaf_key = keys.shift
|
128
|
-
key_hash = keys.inject({leaf_key.to_sym => value}) { |hash, key| {key.to_sym => hash} }
|
129
|
-
deep_merge!(deep_hash, key_hash)
|
130
|
-
deep_hash
|
131
|
-
end
|
132
|
-
end
|
133
|
-
|
134
|
-
# deep_merge by Stefan Rusterholz, see http://www.ruby-forum.com/topic/142809
|
135
|
-
def self.deep_merge!(hash1, hash2)
|
136
|
-
merger = proc { |key, v1, v2| Hash === v1 && Hash === v2 ? v1.merge(v2, &merger) : v2 }
|
137
|
-
hash1.merge!(hash2, &merger)
|
138
|
-
end
|
139
|
-
|
140
|
-
# Convert something like:
|
141
|
-
#
|
142
|
-
# {'0' => "elem 1", '1' => "elem 2"}
|
143
|
-
#
|
144
|
-
# to:
|
145
|
-
#
|
146
|
-
# ["elem 1", "elem 2"]
|
147
|
-
#
|
148
|
-
def self.arraylize(input_hash)
|
149
|
-
input_hash.inject([]) do |constructed_array, (key, value)|
|
150
|
-
constructed_array << value
|
151
|
-
constructed_array
|
152
|
-
end
|
153
|
-
end
|
154
|
-
|
155
|
-
private
|
156
|
-
|
157
|
-
def extract_files
|
158
|
-
files_to_scan.inject(HashWithIndifferentAccess.new) do |files, file|
|
159
|
-
keys = IO.read(file)
|
160
|
-
if keys.respond_to? "encode"
|
161
|
-
keys = keys.encode("UTF-8").force_encoding("UTF-8")
|
162
|
-
end
|
163
|
-
error_count = 0
|
164
|
-
begin
|
165
|
-
encoded_keys = keys.scan(i18n_lookup_pattern)
|
166
|
-
rescue => e
|
167
|
-
unless error_count > 1
|
168
|
-
if keys.respond_to? 'encode!'
|
169
|
-
keys.encode!('utf-8', 'utf-8', :invalid => :replace)
|
170
|
-
end
|
171
|
-
error_count += 1
|
172
|
-
retry
|
173
|
-
else
|
174
|
-
puts "cannot fix: #{e} on : #{file}"
|
175
|
-
end
|
176
|
-
end
|
177
|
-
encoded_keys.flatten.map(&:to_sym).each do |key|
|
178
|
-
files[key] ||= []
|
179
|
-
path = Pathname.new(File.expand_path(file)).relative_path_from(Pathname.new(Rails.root)).to_s
|
180
|
-
files[key] << path if !files[key].include?(path)
|
181
|
-
end
|
182
|
-
files
|
183
|
-
end
|
184
|
-
end
|
185
|
-
|
186
|
-
def i18n_lookup_pattern
|
187
|
-
/\b(?:I18n\.t|I18n\.translate|t)(?:\s|\():?'([a-z0-9_]+.[a-z0-9_.]+)'\)?/
|
188
|
-
end
|
189
|
-
|
190
|
-
def files_to_scan
|
191
|
-
Dir.glob(File.join(Translate::Storage.root_dir, "{app,config,lib}", "**","*.{rb,erb,rhtml}")) +
|
192
|
-
Dir.glob(File.join(Translate::Storage.root_dir, "public", "javascripts", "**","*.js"))
|
193
|
-
end
|
194
|
-
end
|
1
|
+
require 'pathname'
|
2
|
+
|
3
|
+
class Translate::Keys
|
4
|
+
# Allows keys extracted from lookups in files to be cached
|
5
|
+
def self.files
|
6
|
+
@@files ||= Translate::Keys.new.files
|
7
|
+
end
|
8
|
+
|
9
|
+
# Allows flushing of the files cache
|
10
|
+
def self.files=(files)
|
11
|
+
@@files = files
|
12
|
+
end
|
13
|
+
|
14
|
+
def files
|
15
|
+
@files ||= extract_files
|
16
|
+
end
|
17
|
+
alias_method :to_hash, :files
|
18
|
+
|
19
|
+
def keys
|
20
|
+
files.keys
|
21
|
+
end
|
22
|
+
alias_method :to_a, :keys
|
23
|
+
|
24
|
+
def i18n_keys(locale)
|
25
|
+
I18n.backend.send(:init_translations) unless I18n.backend.initialized?
|
26
|
+
Translate::Keys.to_shallow_hash(I18n.backend.send(:translations)[locale.to_sym]).keys.sort
|
27
|
+
end
|
28
|
+
|
29
|
+
def duplicate_keys
|
30
|
+
Translate::Keys.translated_locales.inject({}) do |missing, locale|
|
31
|
+
missing[locale] = i18n_keys(I18n.default_locale).map do |key|
|
32
|
+
I18n.backend.send(:lookup, locale, key) == I18n.backend.send(:lookup, I18n.default_locale, key) ? key : nil
|
33
|
+
end.compact
|
34
|
+
missing
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def untranslated_keys
|
39
|
+
Translate::Keys.translated_locales.inject({}) do |missing, locale|
|
40
|
+
missing[locale] = i18n_keys(I18n.default_locale).map do |key|
|
41
|
+
I18n.backend.send(:lookup, locale, key).nil? ? key : nil
|
42
|
+
end.compact
|
43
|
+
missing
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def missing_keys
|
48
|
+
locale = I18n.default_locale; yaml_keys = {}
|
49
|
+
yaml_keys = Translate::Storage.file_paths(locale).inject({}) do |keys, path|
|
50
|
+
keys = keys.deep_merge(Translate::File.new(path).read[locale.to_s])
|
51
|
+
end
|
52
|
+
files.reject { |key, file| self.class.contains_key?(yaml_keys, key) }
|
53
|
+
end
|
54
|
+
|
55
|
+
def self.translated_locales
|
56
|
+
I18n.available_locales.reject { |locale| [:root, I18n.default_locale.to_sym].include?(locale) }
|
57
|
+
end
|
58
|
+
|
59
|
+
# Checks if a nested hash contains the keys in dot separated I18n key.
|
60
|
+
#
|
61
|
+
# Example:
|
62
|
+
#
|
63
|
+
# hash = {
|
64
|
+
# :foo => {
|
65
|
+
# :bar => {
|
66
|
+
# :baz => 1
|
67
|
+
# }
|
68
|
+
# }
|
69
|
+
# }
|
70
|
+
#
|
71
|
+
# contains_key?("foo", key) # => true
|
72
|
+
# contains_key?("foo.bar", key) # => true
|
73
|
+
# contains_key?("foo.bar.baz", key) # => true
|
74
|
+
# contains_key?("foo.bar.baz.bla", key) # => false
|
75
|
+
#
|
76
|
+
def self.contains_key?(hash, key)
|
77
|
+
keys = key.to_s.split(".")
|
78
|
+
return false if keys.empty?
|
79
|
+
!keys.inject(HashWithIndifferentAccess.new(hash)) do |memo, key|
|
80
|
+
memo.is_a?(Hash) ? memo.try(:[], key) : nil
|
81
|
+
end.nil?
|
82
|
+
end
|
83
|
+
|
84
|
+
# Convert something like:
|
85
|
+
#
|
86
|
+
# {
|
87
|
+
# :pressrelease => {
|
88
|
+
# :label => {
|
89
|
+
# :one => "Pressmeddelande"
|
90
|
+
# }
|
91
|
+
# }
|
92
|
+
# }
|
93
|
+
#
|
94
|
+
# to:
|
95
|
+
#
|
96
|
+
# {'pressrelease.label.one' => "Pressmeddelande"}
|
97
|
+
#
|
98
|
+
def self.to_shallow_hash(hash)
|
99
|
+
hash.inject({}) do |shallow_hash, (key, value)|
|
100
|
+
if value.is_a?(Hash)
|
101
|
+
to_shallow_hash(value).each do |sub_key, sub_value|
|
102
|
+
shallow_hash[[key, sub_key].join(".")] = sub_value
|
103
|
+
end
|
104
|
+
else
|
105
|
+
shallow_hash[key.to_s] = value
|
106
|
+
end
|
107
|
+
shallow_hash
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
# Convert something like:
|
112
|
+
#
|
113
|
+
# {'pressrelease.label.one' => "Pressmeddelande"}
|
114
|
+
#
|
115
|
+
# to:
|
116
|
+
#
|
117
|
+
# {
|
118
|
+
# :pressrelease => {
|
119
|
+
# :label => {
|
120
|
+
# :one => "Pressmeddelande"
|
121
|
+
# }
|
122
|
+
# }
|
123
|
+
# }
|
124
|
+
def self.to_deep_hash(hash)
|
125
|
+
hash.inject({}) do |deep_hash, (key, value)|
|
126
|
+
keys = key.to_s.split('.').reverse
|
127
|
+
leaf_key = keys.shift
|
128
|
+
key_hash = keys.inject({leaf_key.to_sym => value}) { |hash, key| {key.to_sym => hash} }
|
129
|
+
deep_merge!(deep_hash, key_hash)
|
130
|
+
deep_hash
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
# deep_merge by Stefan Rusterholz, see http://www.ruby-forum.com/topic/142809
|
135
|
+
def self.deep_merge!(hash1, hash2)
|
136
|
+
merger = proc { |key, v1, v2| Hash === v1 && Hash === v2 ? v1.merge(v2, &merger) : v2 }
|
137
|
+
hash1.merge!(hash2, &merger)
|
138
|
+
end
|
139
|
+
|
140
|
+
# Convert something like:
|
141
|
+
#
|
142
|
+
# {'0' => "elem 1", '1' => "elem 2"}
|
143
|
+
#
|
144
|
+
# to:
|
145
|
+
#
|
146
|
+
# ["elem 1", "elem 2"]
|
147
|
+
#
|
148
|
+
def self.arraylize(input_hash)
|
149
|
+
input_hash.inject([]) do |constructed_array, (key, value)|
|
150
|
+
constructed_array << value
|
151
|
+
constructed_array
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
private
|
156
|
+
|
157
|
+
def extract_files
|
158
|
+
files_to_scan.inject(HashWithIndifferentAccess.new) do |files, file|
|
159
|
+
keys = IO.read(file)
|
160
|
+
if keys.respond_to? "encode"
|
161
|
+
keys = keys.encode("UTF-8").force_encoding("UTF-8")
|
162
|
+
end
|
163
|
+
error_count = 0
|
164
|
+
begin
|
165
|
+
encoded_keys = keys.scan(i18n_lookup_pattern)
|
166
|
+
rescue => e
|
167
|
+
unless error_count > 1
|
168
|
+
if keys.respond_to? 'encode!'
|
169
|
+
keys.encode!('utf-8', 'utf-8', :invalid => :replace)
|
170
|
+
end
|
171
|
+
error_count += 1
|
172
|
+
retry
|
173
|
+
else
|
174
|
+
puts "cannot fix: #{e} on : #{file}"
|
175
|
+
end
|
176
|
+
end
|
177
|
+
encoded_keys.flatten.map(&:to_sym).each do |key|
|
178
|
+
files[key] ||= []
|
179
|
+
path = Pathname.new(File.expand_path(file)).relative_path_from(Pathname.new(Rails.root)).to_s
|
180
|
+
files[key] << path if !files[key].include?(path)
|
181
|
+
end
|
182
|
+
files
|
183
|
+
end
|
184
|
+
end
|
185
|
+
|
186
|
+
def i18n_lookup_pattern
|
187
|
+
/\b(?:I18n\.t|I18n\.translate|t)(?:\s|\():?'([a-z0-9_]+.[a-z0-9_.]+)'\)?/
|
188
|
+
end
|
189
|
+
|
190
|
+
def files_to_scan
|
191
|
+
Dir.glob(File.join(Translate::Storage.root_dir, "{app,config,lib}", "**","*.{rb,erb,rhtml}")) +
|
192
|
+
Dir.glob(File.join(Translate::Storage.root_dir, "public", "javascripts", "**","*.js"))
|
193
|
+
end
|
194
|
+
end
|