rails-i18nterface 0.1.3 → 0.1.4
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/README.md +8 -4
- data/app/controllers/rails_i18nterface/translate_controller.rb +8 -52
- data/lib/rails-i18nterface.rb +3 -1
- data/lib/rails-i18nterface/keys.rb +115 -164
- data/lib/rails-i18nterface/log.rb +30 -28
- data/lib/rails-i18nterface/sourcefiles.rb +65 -0
- data/lib/rails-i18nterface/storage.rb +23 -21
- data/lib/rails-i18nterface/utils.rb +28 -0
- data/lib/rails-i18nterface/version.rb +1 -1
- data/lib/rails-i18nterface/yamlfile.rb +37 -0
- data/spec/controllers/translate_controller_spec.rb +1 -1
- data/spec/internal/db/combustion_test.sqlite +0 -0
- data/spec/internal/db/schema.rb +14 -0
- data/spec/internal/log/test.log +512 -305
- data/spec/lib/keys_spec.rb +1 -1
- data/spec/lib/log_spec.rb +2 -2
- data/spec/lib/sourcefiles_spec.rb +23 -0
- data/spec/lib/storage_spec.rb +1 -1
- data/spec/lib/utils_spec.rb +19 -0
- data/spec/lib/yamlfile_spec.rb +33 -0
- metadata +69 -49
- checksums.yaml +0 -7
- data/lib/rails-i18nterface/file.rb +0 -35
- data/spec/lib/file_spec.rb +0 -53
data/README.md
CHANGED
@@ -8,21 +8,23 @@ This is a fork of an overhaul of a fork of a fork of rails-translate.
|
|
8
8
|
|
9
9
|
It was originally created by [Peter Marklund and Joakim Westerlund @ mynewsdesk](https://github.com/mynewsdesk/translate)
|
10
10
|
and later adapted to rails 3.0 by [Claudius Coenen](https://github.com/ccoenen/rails-translate).
|
11
|
-
This version is a
|
11
|
+
This version is a spin-off of Claudius Coenen's version by [Larry Sprock](https://github.com/lardawge/rails-i18nterface).
|
12
12
|
It was renamed, refactored and prepared for rails 3.1 as an Engine. Over this work
|
13
13
|
[Michal Hantl](https://github.com/hakunin/rails-i18nterface) made a bunch of nice UI modifications
|
14
|
-
on his fork. Since then it was more or less
|
14
|
+
on his fork. Since then it was more or less abandoned.
|
15
15
|
|
16
16
|
I took over the evolution with some new features:
|
17
17
|
|
18
18
|
* testing using [combustion](https://github.com/pat/combustion) and [rspec](https://github.com/rspec/rspec)
|
19
19
|
* redesign of the layout
|
20
|
-
* navigation overhaul,
|
20
|
+
* navigation overhaul, splitting the name-spaces in a foldable menu
|
21
21
|
* gathering of first-level translations under a ROOT container
|
22
22
|
* gemification and release of a version 0.1.0
|
23
23
|
* (the 0.0.1 was the work from Larry Sprock but was not published as a gem)
|
24
24
|
* compatibility with rails 4 and ruby 2
|
25
25
|
|
26
|
+
Check the [Changelog](https://github.com/mose/rails-i18nterface/blob/master/changelog.md) for details about further changes.
|
27
|
+
|
26
28
|

|
27
29
|
|
28
30
|
## Usage
|
@@ -65,7 +67,7 @@ end
|
|
65
67
|
(this example is for devise users, but you can adjust it to return true or false
|
66
68
|
according to your own context).
|
67
69
|
|
68
|
-
###
|
70
|
+
### Configuration
|
69
71
|
|
70
72
|
You can configure `from_locales` and `to_locales` explicitly in your
|
71
73
|
`environments/development.rb` by adding
|
@@ -81,6 +83,8 @@ Where `[:en]` and `[:ja, :es, :fr]` could be replaced by locale list of your cho
|
|
81
83
|
* extract code from the controller to a lib
|
82
84
|
* refactor the libs in a cleaner way
|
83
85
|
* apply rubocop and follow his law
|
86
|
+
* make the application thread-safe
|
87
|
+
* remove those damn global variables
|
84
88
|
* extend testing to refactored libs
|
85
89
|
* change navigation to an ajax-driven reload
|
86
90
|
* add a way to gather .one and .other and .few under same translation line
|
@@ -1,12 +1,14 @@
|
|
1
1
|
module RailsI18nterface
|
2
2
|
class TranslateController < RailsI18nterface::ApplicationController
|
3
3
|
|
4
|
+
include Utils
|
5
|
+
|
4
6
|
before_filter :init_translations
|
5
7
|
before_filter :set_locale
|
6
8
|
|
7
9
|
def index
|
8
10
|
@dbvalues = {}
|
9
|
-
initialize_keys
|
11
|
+
@keys = initialize_keys
|
10
12
|
load_db_translations
|
11
13
|
@deep_keys = RailsI18nterface::Keys.to_deep_hash(@keys)
|
12
14
|
filter_by_key_pattern
|
@@ -46,61 +48,15 @@ module RailsI18nterface
|
|
46
48
|
locale = params[:locale].to_sym
|
47
49
|
keys = {locale => I18n.backend.send(:translations)[locale] || {}}
|
48
50
|
Translation.where(:locale => @to_locale).each { |translation|
|
49
|
-
next if translation.value == ''
|
50
|
-
next if !translation.value
|
51
|
+
next if !translation.value or translation.value == ''
|
51
52
|
set_nested(keys[locale], translation.key.split("."), translation.value)
|
52
53
|
}
|
53
54
|
remove_blanks keys
|
54
|
-
yaml = keys_to_yaml(
|
55
|
+
yaml = RailsI18nterface::Yamlfile.new.keys_to_yaml(keys)
|
55
56
|
response.headers['Content-Disposition'] = "attachment; filename=#{locale}.yml"
|
56
57
|
render :text => yaml
|
57
58
|
end
|
58
59
|
|
59
|
-
def remove_blanks hash
|
60
|
-
hash.each { |k, v|
|
61
|
-
if !v || v == ''
|
62
|
-
hash.delete k
|
63
|
-
end
|
64
|
-
if v.is_a? Hash
|
65
|
-
remove_blanks v
|
66
|
-
if v == {}
|
67
|
-
hash.delete k
|
68
|
-
end
|
69
|
-
end
|
70
|
-
}
|
71
|
-
end
|
72
|
-
|
73
|
-
def set_nested(hash, key, value)
|
74
|
-
if key.length == 1
|
75
|
-
hash[key[0]] = value
|
76
|
-
else
|
77
|
-
k = key.shift
|
78
|
-
set_nested(hash[k] ||= {}, key, value)
|
79
|
-
end
|
80
|
-
end
|
81
|
-
|
82
|
-
private
|
83
|
-
|
84
|
-
# Stringifying keys for prettier YAML
|
85
|
-
def deep_stringify_keys(hash)
|
86
|
-
hash.inject({}) { |result, (key, value)|
|
87
|
-
value = deep_stringify_keys(value) if value.is_a? Hash
|
88
|
-
result[(key.to_s rescue key) || key] = value
|
89
|
-
result
|
90
|
-
}
|
91
|
-
end
|
92
|
-
|
93
|
-
def keys_to_yaml(keys)
|
94
|
-
# Using ya2yaml, if available, for UTF8 support
|
95
|
-
if keys.respond_to?(:ya2yaml)
|
96
|
-
keys.ya2yaml(:escape_as_utf8 => true)
|
97
|
-
else
|
98
|
-
keys.to_yaml
|
99
|
-
end
|
100
|
-
end
|
101
|
-
|
102
|
-
public
|
103
|
-
|
104
60
|
def update
|
105
61
|
params[:key].each { |k, v|
|
106
62
|
t = Translation.where(:key => k, :locale => @to_locale).first
|
@@ -134,8 +90,8 @@ module RailsI18nterface
|
|
134
90
|
|
135
91
|
def initialize_keys
|
136
92
|
@files = RailsI18nterface::Keys.files
|
137
|
-
|
138
|
-
|
93
|
+
keys = (@files.keys.map(&:to_s) + RailsI18nterface::Keys.new.i18n_keys(@from_locale)).uniq
|
94
|
+
keys.reject! do |key|
|
139
95
|
from_text = lookup(@from_locale, key)
|
140
96
|
# When translating from one language to another, make sure there is a text to translate from.
|
141
97
|
# Always exclude non string translation objects as we don't support editing them in the UI.
|
@@ -227,7 +183,7 @@ module RailsI18nterface
|
|
227
183
|
end
|
228
184
|
|
229
185
|
def per_page
|
230
|
-
50
|
186
|
+
params[:per_page] ? params[:per_page].to_i : 50
|
231
187
|
end
|
232
188
|
helper_method :per_page
|
233
189
|
|
data/lib/rails-i18nterface.rb
CHANGED
@@ -1,8 +1,10 @@
|
|
1
1
|
require "rails-i18nterface/engine"
|
2
|
-
require "rails-i18nterface/
|
2
|
+
require "rails-i18nterface/yamlfile"
|
3
|
+
require "rails-i18nterface/sourcefiles"
|
3
4
|
require "rails-i18nterface/keys"
|
4
5
|
require "rails-i18nterface/log"
|
5
6
|
require "rails-i18nterface/storage"
|
7
|
+
require "rails-i18nterface/utils"
|
6
8
|
|
7
9
|
module RailsI18nterface
|
8
10
|
end
|
@@ -1,192 +1,143 @@
|
|
1
1
|
#require 'pathname'
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
def self.files
|
6
|
-
@@files ||= new.files
|
7
|
-
end
|
8
|
-
|
9
|
-
def self.names
|
10
|
-
@@names || new.names
|
11
|
-
end
|
12
|
-
# Allows flushing of the files cache
|
13
|
-
def self.files=(files)
|
14
|
-
@@files = files
|
15
|
-
end
|
16
|
-
|
17
|
-
def files
|
18
|
-
@files ||= extract_files
|
19
|
-
end
|
20
|
-
alias_method :to_hash, :files
|
21
|
-
|
22
|
-
def names
|
23
|
-
@names ||= build_namespaces
|
24
|
-
end
|
25
|
-
alias_method :to_tree, :names
|
3
|
+
module RailsI18nterface
|
4
|
+
class Keys
|
26
5
|
|
27
|
-
|
28
|
-
files
|
29
|
-
|
30
|
-
|
6
|
+
# Allows keys extracted from lookups in files to be cached
|
7
|
+
def self.files
|
8
|
+
@@files ||= new.files
|
9
|
+
end
|
31
10
|
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
11
|
+
def self.names
|
12
|
+
@@names || new.names
|
13
|
+
end
|
14
|
+
# Allows flushing of the files cache
|
15
|
+
def self.files=(files)
|
16
|
+
@@files = files
|
17
|
+
end
|
36
18
|
|
37
|
-
|
38
|
-
|
39
|
-
missing[locale] = i18n_keys(I18n.default_locale).map do |key|
|
40
|
-
I18n.backend.send(:lookup, locale, key).nil? ? key : nil
|
41
|
-
end.compact
|
42
|
-
missing
|
19
|
+
def files
|
20
|
+
@files ||= RailsI18nterface::Sourcefiles.extract_files
|
43
21
|
end
|
44
|
-
|
22
|
+
alias_method :to_hash, :files
|
45
23
|
|
46
|
-
|
47
|
-
|
48
|
-
yaml_keys = {}
|
49
|
-
yaml_keys = RailsI18nterface::Storage.file_paths(locale).inject({}) do |keys, path|
|
50
|
-
keys = keys.deep_merge(RailsI18nterface::File.new(path).read[locale.to_s])
|
24
|
+
def names
|
25
|
+
@names ||= i18n_keys
|
51
26
|
end
|
52
|
-
|
53
|
-
end
|
27
|
+
alias_method :to_tree, :names
|
54
28
|
|
55
|
-
|
56
|
-
|
57
|
-
|
29
|
+
def keys
|
30
|
+
files.keys
|
31
|
+
end
|
32
|
+
alias_method :to_a, :keys
|
58
33
|
|
59
|
-
|
60
|
-
|
61
|
-
|
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
|
34
|
+
def i18n_keys(locale)
|
35
|
+
I18n.backend.send(:init_translations) unless I18n.backend.initialized?
|
36
|
+
self.class.to_shallow_hash(I18n.backend.send(:translations)[locale.to_sym]).keys.sort
|
37
|
+
end
|
83
38
|
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
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
|
39
|
+
def untranslated_keys
|
40
|
+
self.class.translated_locales.inject({}) do |missing, locale|
|
41
|
+
missing[locale] = i18n_keys(I18n.default_locale).map do |key|
|
42
|
+
I18n.backend.send(:lookup, locale, key).nil? ? key : nil
|
43
|
+
end.compact
|
44
|
+
missing
|
106
45
|
end
|
107
|
-
shallow_hash
|
108
46
|
end
|
109
|
-
end
|
110
47
|
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
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
|
48
|
+
def missing_keys
|
49
|
+
locale = I18n.default_locale
|
50
|
+
yaml_keys = {}
|
51
|
+
yaml_keys = Storage.file_paths(locale).inject({}) do |keys, path|
|
52
|
+
keys = keys.deep_merge(Yamlfile.new(path).read[locale.to_s])
|
53
|
+
end
|
54
|
+
files.reject { |key, file| self.class.contains_key?(yaml_keys, key) }
|
131
55
|
end
|
132
|
-
end
|
133
56
|
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
hash1.merge!(hash2, &merger)
|
138
|
-
end
|
57
|
+
def self.translated_locales
|
58
|
+
I18n.available_locales.reject { |locale| [:root, I18n.default_locale.to_sym].include?(locale) }
|
59
|
+
end
|
139
60
|
|
140
|
-
|
61
|
+
# Checks if a nested hash contains the keys in dot separated I18n key.
|
62
|
+
#
|
63
|
+
# Example:
|
64
|
+
#
|
65
|
+
# hash = {
|
66
|
+
# :foo => {
|
67
|
+
# :bar => {
|
68
|
+
# :baz => 1
|
69
|
+
# }
|
70
|
+
# }
|
71
|
+
# }
|
72
|
+
#
|
73
|
+
# contains_key?("foo", key) # => true
|
74
|
+
# contains_key?("foo.bar", key) # => true
|
75
|
+
# contains_key?("foo.bar.baz", key) # => true
|
76
|
+
# contains_key?("foo.bar.baz.bla", key) # => false
|
77
|
+
#
|
78
|
+
def self.contains_key?(hash, key)
|
79
|
+
keys = key.to_s.split(".")
|
80
|
+
return false if keys.empty?
|
81
|
+
!keys.inject(HashWithIndifferentAccess.new(hash)) do |memo, key|
|
82
|
+
memo.is_a?(Hash) ? memo.try(:[], key) : nil
|
83
|
+
end.nil?
|
84
|
+
end
|
141
85
|
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
86
|
+
# Convert something like:
|
87
|
+
#
|
88
|
+
# {
|
89
|
+
# :pressrelease => {
|
90
|
+
# :label => {
|
91
|
+
# :one => "Pressmeddelande"
|
92
|
+
# }
|
93
|
+
# }
|
94
|
+
# }
|
95
|
+
#
|
96
|
+
# to:
|
97
|
+
#
|
98
|
+
# {'pressrelease.label.one' => "Pressmeddelande"}
|
99
|
+
#
|
100
|
+
def self.to_shallow_hash(hash)
|
101
|
+
hash.inject({}) do |shallow_hash, (key, value)|
|
102
|
+
if value.is_a?(Hash)
|
103
|
+
to_shallow_hash(value).each do |sub_key, sub_value|
|
104
|
+
shallow_hash[[key, sub_key].join(".")] = sub_value
|
150
105
|
end
|
151
|
-
|
152
|
-
|
153
|
-
files[key] ||= []
|
154
|
-
files[key] << path if !files[key].include?(path)
|
106
|
+
else
|
107
|
+
shallow_hash[key.to_s] = value
|
155
108
|
end
|
156
|
-
|
157
|
-
puts e.inspect
|
158
|
-
puts e.backtrace
|
159
|
-
puts "bug in Translation plugin, please debug, informations :"
|
160
|
-
puts file.inspect
|
161
|
-
puts i18n_lookup_pattern.inspect
|
109
|
+
shallow_hash
|
162
110
|
end
|
163
|
-
files
|
164
111
|
end
|
165
|
-
end
|
166
112
|
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
113
|
+
# Convert something like:
|
114
|
+
#
|
115
|
+
# {'pressrelease.label.one' => "Pressmeddelande"}
|
116
|
+
#
|
117
|
+
# to:
|
118
|
+
#
|
119
|
+
# {
|
120
|
+
# :pressrelease => {
|
121
|
+
# :label => {
|
122
|
+
# :one => "Pressmeddelande"
|
123
|
+
# }
|
124
|
+
# }
|
125
|
+
# }
|
126
|
+
def self.to_deep_hash(hash)
|
127
|
+
hash.inject({}) do |deep_hash, (key, value)|
|
128
|
+
keys = key.to_s.split('.').reverse
|
129
|
+
leaf_key = keys.shift
|
130
|
+
key_hash = keys.inject({leaf_key.to_sym => value}) { |hash, key| {key.to_sym => hash} }
|
131
|
+
deep_merge!(deep_hash, key_hash)
|
132
|
+
deep_hash
|
175
133
|
end
|
176
134
|
end
|
177
|
-
parts.join('.')
|
178
|
-
end
|
179
135
|
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
/\b(?:I18n\.t|I18n\.translate|t)(?:\s|\():?(?:'|")(\.?[a-z0-9_\.]+)(?:'|")/
|
186
|
-
end
|
136
|
+
# deep_merge by Stefan Rusterholz, see http://www.ruby-forum.com/topic/142809
|
137
|
+
def self.deep_merge!(hash1, hash2)
|
138
|
+
merger = proc { |key, v1, v2| Hash === v1 && Hash === v2 ? v1.merge(v2, &merger) : v2 }
|
139
|
+
hash1.merge!(hash2, &merger)
|
140
|
+
end
|
187
141
|
|
188
|
-
def files_to_scan
|
189
|
-
Dir.glob(File.join(RailsI18nterface::Storage.root_dir, "{app,config,lib}", "**","*.{rb,erb,haml,slim,rhtml}")) +
|
190
|
-
Dir.glob(File.join(RailsI18nterface::Storage.root_dir, "{public,app/assets}", "javascripts", "**","*.{js,coffee}"))
|
191
142
|
end
|
192
|
-
end
|
143
|
+
end
|