i18n-tasks 0.9.6 → 0.9.7
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +1 -1
- data/Rakefile +2 -1
- data/bin/i18n-tasks +1 -0
- data/config/locales/en.yml +15 -9
- data/config/locales/ru.yml +80 -74
- data/i18n-tasks.gemspec +9 -8
- data/lib/i18n/tasks.rb +2 -5
- data/lib/i18n/tasks/cli.rb +14 -18
- data/lib/i18n/tasks/command/commander.rb +2 -3
- data/lib/i18n/tasks/command/commands/health.rb +1 -1
- data/lib/i18n/tasks/command/commands/meta.rb +2 -2
- data/lib/i18n/tasks/command/commands/missing.rb +17 -19
- data/lib/i18n/tasks/command/commands/tree.rb +3 -3
- data/lib/i18n/tasks/command/commands/usages.rb +6 -5
- data/lib/i18n/tasks/command/commands/xlsx.rb +1 -1
- data/lib/i18n/tasks/command/dsl.rb +1 -2
- data/lib/i18n/tasks/command/option_parsers/enum.rb +6 -6
- data/lib/i18n/tasks/command/option_parsers/locale.rb +3 -1
- data/lib/i18n/tasks/command/options/data.rb +22 -21
- data/lib/i18n/tasks/command_error.rb +1 -3
- data/lib/i18n/tasks/configuration.rb +37 -27
- data/lib/i18n/tasks/console_context.rb +3 -2
- data/lib/i18n/tasks/data.rb +5 -4
- data/lib/i18n/tasks/data/adapter/json_adapter.rb +13 -12
- data/lib/i18n/tasks/data/adapter/yaml_adapter.rb +13 -14
- data/lib/i18n/tasks/data/file_formats.rb +15 -10
- data/lib/i18n/tasks/data/file_system_base.rb +18 -22
- data/lib/i18n/tasks/data/router/conservative_router.rb +3 -6
- data/lib/i18n/tasks/data/router/pattern_router.rb +4 -5
- data/lib/i18n/tasks/data/tree/node.rb +20 -19
- data/lib/i18n/tasks/data/tree/nodes.rb +4 -3
- data/lib/i18n/tasks/data/tree/siblings.rb +36 -29
- data/lib/i18n/tasks/data/tree/traversal.rb +32 -35
- data/lib/i18n/tasks/google_translation.rb +22 -25
- data/lib/i18n/tasks/html_keys.rb +2 -0
- data/lib/i18n/tasks/ignore_keys.rb +3 -2
- data/lib/i18n/tasks/key_pattern_matching.rb +9 -8
- data/lib/i18n/tasks/locale_list.rb +4 -6
- data/lib/i18n/tasks/locale_pathname.rb +8 -8
- data/lib/i18n/tasks/logging.rb +3 -3
- data/lib/i18n/tasks/missing_keys.rb +29 -31
- data/lib/i18n/tasks/plural_keys.rb +6 -7
- data/lib/i18n/tasks/references.rb +72 -41
- data/lib/i18n/tasks/reports/base.rb +8 -7
- data/lib/i18n/tasks/reports/spreadsheet.rb +15 -16
- data/lib/i18n/tasks/reports/terminal.rb +32 -32
- data/lib/i18n/tasks/scanners/file_scanner.rb +6 -5
- data/lib/i18n/tasks/scanners/files/caching_file_finder_provider.rb +1 -2
- data/lib/i18n/tasks/scanners/files/caching_file_reader.rb +0 -1
- data/lib/i18n/tasks/scanners/files/file_finder.rb +12 -15
- data/lib/i18n/tasks/scanners/files/file_reader.rb +0 -1
- data/lib/i18n/tasks/scanners/occurrence_from_position.rb +8 -7
- data/lib/i18n/tasks/scanners/pattern_mapper.rb +2 -2
- data/lib/i18n/tasks/scanners/pattern_scanner.rb +9 -7
- data/lib/i18n/tasks/scanners/pattern_with_scope_scanner.rb +1 -2
- data/lib/i18n/tasks/scanners/relative_keys.rb +11 -11
- data/lib/i18n/tasks/scanners/results/key_occurrences.rb +3 -4
- data/lib/i18n/tasks/scanners/results/occurrence.rb +8 -7
- data/lib/i18n/tasks/scanners/ruby_ast_call_finder.rb +2 -2
- data/lib/i18n/tasks/scanners/ruby_ast_scanner.rb +39 -31
- data/lib/i18n/tasks/scanners/scanner_multiplexer.rb +9 -3
- data/lib/i18n/tasks/split_key.rb +5 -6
- data/lib/i18n/tasks/stats.rb +10 -8
- data/lib/i18n/tasks/string_interpolation.rb +1 -1
- data/lib/i18n/tasks/unused_keys.rb +2 -2
- data/lib/i18n/tasks/used_keys.rb +47 -43
- data/lib/i18n/tasks/version.rb +1 -1
- data/templates/rspec/i18n_spec.rb +2 -2
- metadata +30 -3
@@ -25,9 +25,15 @@ module I18n::Tasks::Scanners
|
|
25
25
|
def collect_results
|
26
26
|
return [@scanners[0].keys] if @scanners.length == 1
|
27
27
|
Array.new(@scanners.length).tap do |results|
|
28
|
-
|
29
|
-
|
30
|
-
|
28
|
+
results_mutex = Mutex.new
|
29
|
+
@scanners.map.with_index do |scanner, i|
|
30
|
+
Thread.start do
|
31
|
+
scanner_results = scanner.keys
|
32
|
+
results_mutex.synchronize do
|
33
|
+
results[i] = scanner_results
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end.each(&:join)
|
31
37
|
end
|
32
38
|
end
|
33
39
|
end
|
data/lib/i18n/tasks/split_key.rb
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
module I18n
|
3
3
|
module Tasks
|
4
4
|
module SplitKey
|
5
|
-
|
5
|
+
module_function
|
6
6
|
|
7
7
|
# split a key by dots (.)
|
8
8
|
# dots inside braces or parenthesis are not split on
|
@@ -37,7 +37,7 @@ module I18n
|
|
37
37
|
return enum_for(:key_parts, key) unless block
|
38
38
|
nesting = PARENS
|
39
39
|
counts = PARENS_ZEROS # dup'd later if key contains parenthesis
|
40
|
-
delim = '.'
|
40
|
+
delim = '.'
|
41
41
|
from = to = 0
|
42
42
|
key.each_char do |char|
|
43
43
|
if char == delim && PARENS_ZEROS == counts
|
@@ -46,7 +46,7 @@ module I18n
|
|
46
46
|
else
|
47
47
|
nest_i, nest_inc = nesting[char]
|
48
48
|
if nest_i
|
49
|
-
counts
|
49
|
+
counts = counts.dup if counts.frozen?
|
50
50
|
counts[nest_i] += nest_inc
|
51
51
|
end
|
52
52
|
to += 1
|
@@ -56,12 +56,11 @@ module I18n
|
|
56
56
|
true
|
57
57
|
end
|
58
58
|
|
59
|
-
PARENS
|
59
|
+
PARENS = %w({} [] ()).each_with_object({}) do |s, h|
|
60
60
|
i = h.size / 2
|
61
61
|
h[s[0].freeze] = [i, 1].freeze
|
62
62
|
h[s[1].freeze] = [i, -1].freeze
|
63
|
-
|
64
|
-
}.freeze
|
63
|
+
end.freeze
|
65
64
|
PARENS_ZEROS = Array.new(PARENS.size, 0).freeze
|
66
65
|
private_constant :PARENS
|
67
66
|
private_constant :PARENS_ZEROS
|
data/lib/i18n/tasks/stats.rb
CHANGED
@@ -2,18 +2,20 @@
|
|
2
2
|
module I18n::Tasks
|
3
3
|
module Stats
|
4
4
|
def forest_stats(forest)
|
5
|
-
key_count
|
5
|
+
key_count = forest.leaves.count
|
6
6
|
locale_count = forest.count
|
7
7
|
if key_count.zero?
|
8
|
-
{key_count: 0}
|
8
|
+
{ key_count: 0 }
|
9
9
|
else
|
10
10
|
{
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
11
|
+
locales: forest.map(&:key).join(', '),
|
12
|
+
key_count: key_count,
|
13
|
+
locale_count: locale_count,
|
14
|
+
per_locale_avg: forest.inject(0) { |sum, f| sum + f.leaves.count } / locale_count,
|
15
|
+
key_segments_avg: format(
|
16
|
+
'%.1f', forest.leaves.inject(0) { |sum, node| sum + node.walk_to_root.count - 1 } / key_count.to_f
|
17
|
+
),
|
18
|
+
value_chars_avg: forest.leaves.inject(0) { |sum, node| sum + node.value.to_s.length } / key_count
|
17
19
|
}
|
18
20
|
end
|
19
21
|
end
|
@@ -15,8 +15,8 @@ module I18n
|
|
15
15
|
used_key_names = used_tree(strict: true).key_names
|
16
16
|
collapse_plural_nodes! data[locale].select_keys { |key, _node|
|
17
17
|
!ignore_key?(key, :unused) &&
|
18
|
-
|
19
|
-
|
18
|
+
(strict || !used_in_expr?(key)) &&
|
19
|
+
!used_key_names.include?(depluralize_key(key, locale))
|
20
20
|
}
|
21
21
|
end
|
22
22
|
end
|
data/lib/i18n/tasks/used_keys.rb
CHANGED
@@ -6,25 +6,30 @@ require 'i18n/tasks/scanners/scanner_multiplexer'
|
|
6
6
|
require 'i18n/tasks/scanners/files/caching_file_finder_provider'
|
7
7
|
require 'i18n/tasks/scanners/files/caching_file_reader'
|
8
8
|
|
9
|
+
# Require the pattern mapper even though it's not used by i18n-tasks directly.
|
10
|
+
# This allows the user to use it in config/i18n-tasks.yml without having to require it.
|
11
|
+
# See https://github.com/glebm/i18n-tasks/issues/204.
|
12
|
+
require 'i18n/tasks/scanners/pattern_mapper'
|
13
|
+
|
9
14
|
module I18n::Tasks
|
10
|
-
module UsedKeys
|
15
|
+
module UsedKeys # rubocop:disable Metrics/ModuleLength
|
11
16
|
SEARCH_DEFAULTS = {
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
}.tap
|
17
|
+
paths: %w(app/).freeze,
|
18
|
+
relative_roots: %w(app/controllers app/helpers app/mailers app/presenters app/views).freeze,
|
19
|
+
scanners: [['::I18n::Tasks::Scanners::RubyAstScanner', only: %w(*.rb)]],
|
20
|
+
strict: true
|
21
|
+
}.tap do |defaults|
|
17
22
|
defaults[:scanners] << ['::I18n::Tasks::Scanners::PatternWithScopeScanner',
|
18
23
|
exclude: defaults[:scanners].map { |(_, opts)| opts[:only] }.reduce(:+).freeze,
|
19
|
-
ignore_lines: {'opal'
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
24
|
+
ignore_lines: { 'opal' => %q(^\s*#(?!\si18n-tasks-use)),
|
25
|
+
'haml' => %q(^\s*-\s*#(?!\si18n-tasks-use)),
|
26
|
+
'slim' => %q(^\s*(?:-#|/)(?!\si18n-tasks-use)),
|
27
|
+
'coffee' => %q(^\s*#(?!\si18n-tasks-use)),
|
28
|
+
'erb' => %q(^\s*<%\s*#(?!\si18n-tasks-use)) }.freeze]
|
29
|
+
end
|
25
30
|
|
26
31
|
ALWAYS_EXCLUDE = %w(*.jpg *.png *.gif *.svg *.ico *.eot *.otf *.ttf *.woff *.woff2 *.pdf *.css *.sass *.scss *.less
|
27
|
-
*.yml *.json *.zip *.tar.gz)
|
32
|
+
*.yml *.json *.zip *.tar.gz).freeze
|
28
33
|
|
29
34
|
# Find all keys in the source and return a forest with the keys in absolute form and their occurrences.
|
30
35
|
#
|
@@ -41,28 +46,25 @@ module I18n::Tasks
|
|
41
46
|
src_tree.tap do |result|
|
42
47
|
tree = result['used'].children
|
43
48
|
tree.subtract_by_key!(raw_refs)
|
44
|
-
if include_raw_references
|
45
|
-
tree.merge!(raw_refs)
|
46
|
-
end
|
49
|
+
tree.merge!(raw_refs) if include_raw_references
|
47
50
|
tree.merge!(used_refs).merge!(resolved_refs)
|
48
51
|
end
|
49
52
|
end
|
50
53
|
|
51
54
|
def used_in_source_tree(key_filter: nil, strict: nil)
|
52
55
|
keys = ((@keys_used_in_source_tree ||= {})[strict?(strict)] ||=
|
53
|
-
|
56
|
+
scanner(strict: strict).keys.freeze)
|
54
57
|
if key_filter
|
55
58
|
key_filter_re = compile_key_pattern(key_filter)
|
56
59
|
keys = keys.reject { |k| k.key !~ key_filter_re }
|
57
60
|
end
|
58
61
|
Data::Tree::Node.new(
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
+
key: 'used',
|
63
|
+
data: { key_filter: key_filter },
|
64
|
+
children: Data::Tree::Siblings.from_key_occurrences(keys)
|
62
65
|
).to_siblings
|
63
66
|
end
|
64
67
|
|
65
|
-
|
66
68
|
def scanner(strict: nil)
|
67
69
|
(@scanner ||= {})[strict?(strict)] ||= begin
|
68
70
|
shared_options = search_config.dup
|
@@ -70,16 +72,18 @@ module I18n::Tasks
|
|
70
72
|
shared_options[:strict] = strict unless strict.nil?
|
71
73
|
log_verbose 'Scanners: '
|
72
74
|
Scanners::ScannerMultiplexer.new(
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
75
|
+
scanners: search_config[:scanners].map do |(class_name, args)|
|
76
|
+
if args && args[:strict]
|
77
|
+
fail CommandError, 'the strict option is global and cannot be applied on the scanner level'
|
78
|
+
end
|
79
|
+
|
80
|
+
ActiveSupport::Inflector.constantize(class_name).new(
|
81
|
+
config: merge_scanner_configs(shared_options, args || {}),
|
82
|
+
file_finder_provider: caching_file_finder_provider,
|
83
|
+
file_reader: caching_file_reader
|
84
|
+
)
|
85
|
+
end.tap { |scanners| log_verbose { scanners.map { |s| " #{s.class.name} #{s.config.inspect}" } * "\n" } }
|
86
|
+
)
|
83
87
|
end
|
84
88
|
end
|
85
89
|
|
@@ -122,9 +126,9 @@ module I18n::Tasks
|
|
122
126
|
@caching_file_reader ||= Scanners::Files::CachingFileReader.new
|
123
127
|
end
|
124
128
|
|
125
|
-
# @return whether the key is potentially used in a code expression such as `t("category.#{
|
129
|
+
# @return [Boolean] whether the key is potentially used in a code expression such as `t("category.#{category_key}")`
|
126
130
|
def used_in_expr?(key)
|
127
|
-
!!(key =~ expr_key_re)
|
131
|
+
!!(key =~ expr_key_re) # rubocop:disable Style/DoubleNegation
|
128
132
|
end
|
129
133
|
|
130
134
|
private
|
@@ -137,17 +141,17 @@ module I18n::Tasks
|
|
137
141
|
|
138
142
|
# keys in the source that end with a ., e.g. t("category.#{ cat.i18n_key }") or t("category." + category.key)
|
139
143
|
# @param [String] replacement for interpolated values.
|
140
|
-
def expr_key_re(replacement: ':'
|
144
|
+
def expr_key_re(replacement: ':')
|
141
145
|
@expr_key_re ||= begin
|
142
146
|
# disallow patterns with no keys
|
143
147
|
ignore_pattern_re = /\A[\.#{replacement}]*\z/
|
144
|
-
patterns = used_in_source_tree(strict: false).key_names.select
|
145
|
-
k.end_with?('.'
|
146
|
-
|
147
|
-
pattern = "#{replace_key_exp(k, replacement)}#{replacement if k.end_with?('.'
|
148
|
+
patterns = used_in_source_tree(strict: false).key_names.select do |k|
|
149
|
+
k.end_with?('.') || k =~ /\#{/
|
150
|
+
end.map do |k|
|
151
|
+
pattern = "#{replace_key_exp(k, replacement)}#{replacement if k.end_with?('.')}"
|
148
152
|
next if pattern =~ ignore_pattern_re
|
149
153
|
pattern
|
150
|
-
|
154
|
+
end.compact
|
151
155
|
compile_key_pattern "{#{patterns * ','}}"
|
152
156
|
end
|
153
157
|
end
|
@@ -160,15 +164,15 @@ module I18n::Tasks
|
|
160
164
|
scanner = StringScanner.new(key)
|
161
165
|
braces = []
|
162
166
|
result = []
|
163
|
-
while (match_until = scanner.scan_until(/(?:#?\{|})
|
164
|
-
if scanner.matched == '#{'
|
167
|
+
while (match_until = scanner.scan_until(/(?:#?\{|})/))
|
168
|
+
if scanner.matched == '#{'
|
165
169
|
braces << scanner.matched
|
166
170
|
result << match_until[0..-3] if braces.length == 1
|
167
171
|
elsif scanner.matched == '}'
|
168
172
|
prev_brace = braces.pop
|
169
|
-
result << replacement if braces.empty? && prev_brace == '#{'
|
173
|
+
result << replacement if braces.empty? && prev_brace == '#{'
|
170
174
|
else
|
171
|
-
braces << '{'
|
175
|
+
braces << '{'
|
172
176
|
end
|
173
177
|
end
|
174
178
|
result << key[scanner.pos..-1] unless scanner.eos?
|
data/lib/i18n/tasks/version.rb
CHANGED
@@ -8,11 +8,11 @@ RSpec.describe 'I18n' do
|
|
8
8
|
|
9
9
|
it 'does not have missing keys' do
|
10
10
|
expect(missing_keys).to be_empty,
|
11
|
-
|
11
|
+
"Missing #{missing_keys.leaves.count} i18n keys, run `i18n-tasks missing' to show them"
|
12
12
|
end
|
13
13
|
|
14
14
|
it 'does not have unused keys' do
|
15
15
|
expect(unused_keys).to be_empty,
|
16
|
-
|
16
|
+
"#{unused_keys.leaves.count} unused i18n keys, run `i18n-tasks unused' to show them"
|
17
17
|
end
|
18
18
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: i18n-tasks
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.9.
|
4
|
+
version: 0.9.7
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- glebm
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2016-
|
11
|
+
date: 2016-12-27 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activesupport
|
@@ -192,6 +192,34 @@ dependencies:
|
|
192
192
|
- - "~>"
|
193
193
|
- !ruby/object:Gem::Version
|
194
194
|
version: '3.3'
|
195
|
+
- !ruby/object:Gem::Dependency
|
196
|
+
name: rubocop
|
197
|
+
requirement: !ruby/object:Gem::Requirement
|
198
|
+
requirements:
|
199
|
+
- - ">="
|
200
|
+
- !ruby/object:Gem::Version
|
201
|
+
version: '0'
|
202
|
+
type: :development
|
203
|
+
prerelease: false
|
204
|
+
version_requirements: !ruby/object:Gem::Requirement
|
205
|
+
requirements:
|
206
|
+
- - ">="
|
207
|
+
- !ruby/object:Gem::Version
|
208
|
+
version: '0'
|
209
|
+
- !ruby/object:Gem::Dependency
|
210
|
+
name: simplecov
|
211
|
+
requirement: !ruby/object:Gem::Requirement
|
212
|
+
requirements:
|
213
|
+
- - ">="
|
214
|
+
- !ruby/object:Gem::Version
|
215
|
+
version: '0'
|
216
|
+
type: :development
|
217
|
+
prerelease: false
|
218
|
+
version_requirements: !ruby/object:Gem::Requirement
|
219
|
+
requirements:
|
220
|
+
- - ">="
|
221
|
+
- !ruby/object:Gem::Version
|
222
|
+
version: '0'
|
195
223
|
- !ruby/object:Gem::Dependency
|
196
224
|
name: yard
|
197
225
|
requirement: !ruby/object:Gem::Requirement
|
@@ -330,4 +358,3 @@ signing_key:
|
|
330
358
|
specification_version: 4
|
331
359
|
summary: Manage localization and translation with the awesome power of static analysis
|
332
360
|
test_files: []
|
333
|
-
has_rdoc:
|