i18n-tasks 0.9.2 → 0.9.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +6 -1
- data/lib/i18n/tasks.rb +1 -0
- data/lib/i18n/tasks/base_task.rb +3 -0
- data/lib/i18n/tasks/cli.rb +1 -0
- data/lib/i18n/tasks/command/collection.rb +1 -0
- data/lib/i18n/tasks/command/commander.rb +1 -0
- data/lib/i18n/tasks/command/commands/data.rb +1 -0
- data/lib/i18n/tasks/command/commands/eq_base.rb +1 -0
- data/lib/i18n/tasks/command/commands/health.rb +1 -0
- data/lib/i18n/tasks/command/commands/meta.rb +1 -0
- data/lib/i18n/tasks/command/commands/missing.rb +1 -0
- data/lib/i18n/tasks/command/commands/tree.rb +1 -0
- data/lib/i18n/tasks/command/commands/usages.rb +2 -1
- data/lib/i18n/tasks/command/commands/xlsx.rb +1 -0
- data/lib/i18n/tasks/command/dsl.rb +1 -0
- data/lib/i18n/tasks/command/option_parsers/enum.rb +1 -0
- data/lib/i18n/tasks/command/option_parsers/locale.rb +1 -0
- data/lib/i18n/tasks/command/options/common.rb +1 -0
- data/lib/i18n/tasks/command/options/data.rb +1 -0
- data/lib/i18n/tasks/command/options/locales.rb +1 -0
- data/lib/i18n/tasks/command_error.rb +1 -0
- data/lib/i18n/tasks/commands.rb +1 -0
- data/lib/i18n/tasks/configuration.rb +1 -0
- data/lib/i18n/tasks/console_context.rb +1 -0
- data/lib/i18n/tasks/data.rb +2 -1
- data/lib/i18n/tasks/data/adapter/json_adapter.rb +1 -0
- data/lib/i18n/tasks/data/adapter/yaml_adapter.rb +1 -0
- data/lib/i18n/tasks/data/file_formats.rb +1 -0
- data/lib/i18n/tasks/data/file_system.rb +1 -0
- data/lib/i18n/tasks/data/file_system_base.rb +1 -0
- data/lib/i18n/tasks/data/router/conservative_router.rb +1 -0
- data/lib/i18n/tasks/data/router/pattern_router.rb +1 -0
- data/lib/i18n/tasks/data/tree/node.rb +5 -0
- data/lib/i18n/tasks/data/tree/nodes.rb +1 -0
- data/lib/i18n/tasks/data/tree/siblings.rb +23 -6
- data/lib/i18n/tasks/data/tree/traversal.rb +2 -0
- data/lib/i18n/tasks/google_translation.rb +5 -0
- data/lib/i18n/tasks/html_keys.rb +1 -0
- data/lib/i18n/tasks/ignore_keys.rb +1 -0
- data/lib/i18n/tasks/key_pattern_matching.rb +3 -17
- data/lib/i18n/tasks/locale_list.rb +1 -0
- data/lib/i18n/tasks/locale_pathname.rb +1 -0
- data/lib/i18n/tasks/logging.rb +1 -0
- data/lib/i18n/tasks/missing_keys.rb +2 -1
- data/lib/i18n/tasks/plural_keys.rb +1 -0
- data/lib/i18n/tasks/references.rb +68 -0
- data/lib/i18n/tasks/reports/base.rb +4 -5
- data/lib/i18n/tasks/reports/spreadsheet.rb +1 -0
- data/lib/i18n/tasks/reports/terminal.rb +29 -6
- data/lib/i18n/tasks/scanners/file_scanner.rb +1 -0
- data/lib/i18n/tasks/scanners/files/caching_file_finder.rb +1 -0
- data/lib/i18n/tasks/scanners/files/caching_file_finder_provider.rb +1 -0
- data/lib/i18n/tasks/scanners/files/caching_file_reader.rb +1 -0
- data/lib/i18n/tasks/scanners/files/file_finder.rb +1 -0
- data/lib/i18n/tasks/scanners/files/file_reader.rb +1 -0
- data/lib/i18n/tasks/scanners/occurrence_from_position.rb +4 -2
- data/lib/i18n/tasks/scanners/pattern_scanner.rb +3 -2
- data/lib/i18n/tasks/scanners/pattern_with_scope_scanner.rb +1 -0
- data/lib/i18n/tasks/scanners/relative_keys.rb +1 -0
- data/lib/i18n/tasks/scanners/results/key_occurrences.rb +1 -0
- data/lib/i18n/tasks/scanners/results/occurrence.rb +10 -3
- data/lib/i18n/tasks/scanners/ruby_ast_call_finder.rb +1 -0
- data/lib/i18n/tasks/scanners/ruby_ast_scanner.rb +5 -2
- data/lib/i18n/tasks/scanners/scanner.rb +1 -0
- data/lib/i18n/tasks/scanners/scanner_multiplexer.rb +1 -0
- data/lib/i18n/tasks/split_key.rb +1 -0
- data/lib/i18n/tasks/stats.rb +1 -0
- data/lib/i18n/tasks/string_interpolation.rb +1 -0
- data/lib/i18n/tasks/unused_keys.rb +5 -1
- data/lib/i18n/tasks/used_keys.rb +56 -16
- data/lib/i18n/tasks/version.rb +2 -1
- data/templates/rspec/i18n_spec.rb +1 -0
- metadata +4 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b5b15ad428fdc29cc404d033f363c08708f5b815
|
4
|
+
data.tar.gz: c5aaaecace6c6024d544ff552c06097b9ec2f252
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 54586d8f4fb14861a755a75325ccf73230b9e180bc590f2f7fd383dc6c10c3529943179709d1d8045873148530684625c92a53d9b997281b9078acac51c40699
|
7
|
+
data.tar.gz: 693ff45562b53f09a0c5baf81b0d5418227e35d182f81e51cdff99c740b2a4666c39bbf3ea84f9d64daafb5ce34b454a59410b3c73a03fadf35c3b9ee98244bb
|
data/README.md
CHANGED
@@ -24,7 +24,7 @@ i18n-tasks can be used with any project using the ruby [i18n gem][i18n-gem] (def
|
|
24
24
|
Add i18n-tasks to the Gemfile:
|
25
25
|
|
26
26
|
```ruby
|
27
|
-
gem 'i18n-tasks', '~> 0.9.
|
27
|
+
gem 'i18n-tasks', '~> 0.9.3'
|
28
28
|
```
|
29
29
|
|
30
30
|
Copy the default [configuration file](#configuration):
|
@@ -159,6 +159,11 @@ See the full list of tasks with `i18n-tasks --help`.
|
|
159
159
|
|
160
160
|
✔ Plural keys, such as `key.{one,many,other,...}` are fully supported.
|
161
161
|
|
162
|
+
#### Reference keys
|
163
|
+
|
164
|
+
✔ Reference keys (keys with `:symbol` values) are fully supported. These keys are copied as-is in
|
165
|
+
`add/translate-missing`, and can be looked up by reference or value in `find`.
|
166
|
+
|
162
167
|
#### `t()` keyword arguments
|
163
168
|
|
164
169
|
✔ `scope` keyword argument is fully supported by the AST scanner, and also by the Regexp scanner but only when it is the first argument.
|
data/lib/i18n/tasks.rb
CHANGED
data/lib/i18n/tasks/base_task.rb
CHANGED
@@ -1,8 +1,10 @@
|
|
1
|
+
# frozen_string_literal: true
|
1
2
|
require 'i18n/tasks/command_error'
|
2
3
|
require 'i18n/tasks/split_key'
|
3
4
|
require 'i18n/tasks/key_pattern_matching'
|
4
5
|
require 'i18n/tasks/logging'
|
5
6
|
require 'i18n/tasks/plural_keys'
|
7
|
+
require 'i18n/tasks/references'
|
6
8
|
require 'i18n/tasks/html_keys'
|
7
9
|
require 'i18n/tasks/used_keys'
|
8
10
|
require 'i18n/tasks/ignore_keys'
|
@@ -22,6 +24,7 @@ module I18n
|
|
22
24
|
include SplitKey
|
23
25
|
include KeyPatternMatching
|
24
26
|
include PluralKeys
|
27
|
+
include References
|
25
28
|
include HtmlKeys
|
26
29
|
include UsedKeys
|
27
30
|
include IgnoreKeys
|
data/lib/i18n/tasks/cli.rb
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
# frozen_string_literal: true
|
1
2
|
module I18n::Tasks
|
2
3
|
module Command
|
3
4
|
module Commands
|
@@ -15,7 +16,7 @@ module I18n::Tasks
|
|
15
16
|
|
16
17
|
def find(opt = {})
|
17
18
|
opt[:filter] ||= opt.delete(:pattern) || opt[:arguments].try(:first)
|
18
|
-
print_forest i18n.used_tree(strict: opt[:strict], key_filter: opt[:filter].presence), opt, :used_keys
|
19
|
+
print_forest i18n.used_tree(strict: opt[:strict], key_filter: opt[:filter].presence, include_raw_references: true), opt, :used_keys
|
19
20
|
end
|
20
21
|
|
21
22
|
cmd :unused,
|
data/lib/i18n/tasks/commands.rb
CHANGED
data/lib/i18n/tasks/data.rb
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
# frozen_string_literal: true
|
1
2
|
require 'i18n/tasks/data/file_system'
|
2
3
|
|
3
4
|
module I18n::Tasks
|
@@ -10,7 +11,7 @@ module I18n::Tasks
|
|
10
11
|
# @see I18n::Tasks::Data::FileSystem
|
11
12
|
def data
|
12
13
|
@data ||= begin
|
13
|
-
data_config
|
14
|
+
data_config = (config[:data] || {}).deep_symbolize_keys
|
14
15
|
data_config.merge!(base_locale: base_locale, locales: config[:locales])
|
15
16
|
adapter_class = data_config[:adapter].presence || data_config[:class].presence || DATA_DEFAULTS[:adapter]
|
16
17
|
adapter_class = adapter_class.to_s
|
@@ -1,3 +1,4 @@
|
|
1
|
+
# frozen_string_literal: true
|
1
2
|
|
2
3
|
require 'i18n/tasks/data/tree/traversal'
|
3
4
|
require 'i18n/tasks/data/tree/siblings'
|
@@ -73,6 +74,10 @@ module I18n::Tasks::Data::Tree
|
|
73
74
|
@data.present?
|
74
75
|
end
|
75
76
|
|
77
|
+
def reference?
|
78
|
+
value.is_a?(Symbol)
|
79
|
+
end
|
80
|
+
|
76
81
|
def get(key)
|
77
82
|
children.get(key)
|
78
83
|
end
|
@@ -1,3 +1,4 @@
|
|
1
|
+
# frozen_string_literal: true
|
1
2
|
require 'set'
|
2
3
|
require 'i18n/tasks/split_key'
|
3
4
|
require 'i18n/tasks/data/tree/nodes'
|
@@ -108,13 +109,12 @@ module I18n::Tasks::Data::Tree
|
|
108
109
|
derive.append!(nodes)
|
109
110
|
end
|
110
111
|
|
111
|
-
|
112
|
+
# @param on_leaves_merge [Proc] invoked when a leaf is merged with another leaf
|
113
|
+
def merge!(nodes, on_leaves_merge: nil)
|
112
114
|
nodes = Siblings.from_nested_hash(nodes) if nodes.is_a?(Hash)
|
113
115
|
nodes.each do |node|
|
114
|
-
merge_node! node
|
116
|
+
merge_node! node, on_leaves_merge: on_leaves_merge
|
115
117
|
end
|
116
|
-
@list = key_to_node.values
|
117
|
-
dirty!
|
118
118
|
self
|
119
119
|
end
|
120
120
|
|
@@ -131,10 +131,23 @@ module I18n::Tasks::Data::Tree
|
|
131
131
|
remove_nodes_collapsing_emptied_ancestors to_remove
|
132
132
|
end
|
133
133
|
|
134
|
+
def subtract_keys!(keys)
|
135
|
+
to_remove = Set.new
|
136
|
+
keys.each do |full_key|
|
137
|
+
node = get full_key
|
138
|
+
to_remove << node if node
|
139
|
+
end
|
140
|
+
remove_nodes_collapsing_emptied_ancestors! to_remove
|
141
|
+
end
|
142
|
+
|
134
143
|
def subtract_by_key(other)
|
135
144
|
subtract_keys other.key_names(root: true)
|
136
145
|
end
|
137
146
|
|
147
|
+
def subtract_by_key!(other)
|
148
|
+
subtract_keys! other.key_names(root: true)
|
149
|
+
end
|
150
|
+
|
138
151
|
def set_root_key!(new_key, data = nil)
|
139
152
|
return self if empty?
|
140
153
|
rename_key first.key, new_key
|
@@ -142,7 +155,8 @@ module I18n::Tasks::Data::Tree
|
|
142
155
|
self
|
143
156
|
end
|
144
157
|
|
145
|
-
|
158
|
+
# @param on_leaves_merge [Proc] invoked when a leaf is merged with another leaf
|
159
|
+
def merge_node!(node, on_leaves_merge: nil)
|
146
160
|
if key_to_node.key?(node.key)
|
147
161
|
our = key_to_node[node.key]
|
148
162
|
return if our == node
|
@@ -155,9 +169,12 @@ module I18n::Tasks::Data::Tree
|
|
155
169
|
warn_add_children_to_leaf our
|
156
170
|
our.children = node.children
|
157
171
|
end
|
172
|
+
elsif on_leaves_merge
|
173
|
+
on_leaves_merge.call(our, node)
|
158
174
|
end
|
159
175
|
else
|
160
|
-
key_to_node[node.key] = node.derive(parent: parent)
|
176
|
+
@list << (key_to_node[node.key] = node.derive(parent: parent))
|
177
|
+
dirty!
|
161
178
|
end
|
162
179
|
end
|
163
180
|
|
@@ -1,3 +1,4 @@
|
|
1
|
+
# frozen_string_literal: true
|
1
2
|
module I18n::Tasks
|
2
3
|
module Data::Tree
|
3
4
|
# Any Enumerable that yields nodes can mix in this module
|
@@ -145,6 +146,7 @@ module I18n::Tasks
|
|
145
146
|
def set_each_value!(val_pattern, key_pattern = nil, &value_proc)
|
146
147
|
value_proc ||= proc { |node|
|
147
148
|
node_value = node.value
|
149
|
+
next node_value if node.reference?
|
148
150
|
human_key = ActiveSupport::Inflector.humanize(node.key.to_s)
|
149
151
|
full_key = node.full_key
|
150
152
|
StringInterpolation.interpolate_soft(
|
@@ -1,3 +1,4 @@
|
|
1
|
+
# frozen_string_literal: true
|
1
2
|
require 'easy_translate'
|
2
3
|
require 'i18n/tasks/html_keys'
|
3
4
|
|
@@ -22,9 +23,13 @@ module I18n::Tasks
|
|
22
23
|
opts[:key] ||= translation_config[:api_key]
|
23
24
|
validate_google_translate_api_key! opts[:key]
|
24
25
|
key_pos = list.each_with_index.inject({}) { |idx, ((k, _v), i)| idx[k] = i; idx }
|
26
|
+
# copy reference keys as is, instead of translating
|
27
|
+
reference_key_vals = list.select { |_k, v| v.is_a? Symbol } || []
|
28
|
+
list -= reference_key_vals
|
25
29
|
result = list.group_by { |k_v| HtmlKeys.html_key? k_v[0] }.map { |is_html, list_slice|
|
26
30
|
fetch_google_translations list_slice, opts.merge(is_html ? {html: true} : {format: 'text'})
|
27
31
|
}.reduce(:+) || []
|
32
|
+
result.concat(reference_key_vals)
|
28
33
|
result.sort! { |a, b| key_pos[a[0]] <=> key_pos[b[0]] }
|
29
34
|
result
|
30
35
|
end
|
data/lib/i18n/tasks/html_keys.rb
CHANGED
@@ -1,3 +1,6 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require 'strscan'
|
3
|
+
|
1
4
|
module I18n::Tasks::KeyPatternMatching
|
2
5
|
extend self
|
3
6
|
MATCH_NOTHING = /\z\A/.freeze
|
@@ -29,21 +32,4 @@ module I18n::Tasks::KeyPatternMatching
|
|
29
32
|
gsub(/:/, '(?<=^|\.)[^.]+?(?=\.|$)'.freeze).
|
30
33
|
gsub(/\{(.*?)}/) { "(#{$1.strip.gsub /\s*,\s*/, '|'.freeze})" }
|
31
34
|
end
|
32
|
-
|
33
|
-
def key_match_pattern(k)
|
34
|
-
@key_match_pattern ||= {}
|
35
|
-
@key_match_pattern[k] ||= begin
|
36
|
-
"#{k.gsub(KEY_INTERPOLATION_RE, ':'.freeze)}#{':'.freeze if k.end_with?('.'.freeze)}"
|
37
|
-
end
|
38
|
-
end
|
39
|
-
|
40
|
-
# @return true if the key looks like an expression
|
41
|
-
KEY_INTERPOLATION_RE = /(?:\#{.*?}|\*+|\:+)/.freeze
|
42
|
-
def key_expression?(k)
|
43
|
-
@key_is_expr ||= {}
|
44
|
-
if @key_is_expr[k].nil?
|
45
|
-
@key_is_expr[k] = (k =~ KEY_INTERPOLATION_RE || k.end_with?('.'.freeze))
|
46
|
-
end
|
47
|
-
@key_is_expr[k]
|
48
|
-
end
|
49
35
|
end
|
data/lib/i18n/tasks/logging.rb
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
# frozen_string_literal: true
|
1
2
|
require 'set'
|
2
3
|
module I18n::Tasks
|
3
4
|
module MissingKeys
|
@@ -79,7 +80,7 @@ module I18n::Tasks
|
|
79
80
|
base = data[compare_to].first.children
|
80
81
|
data[locale].select_keys(root: false) { |key, node|
|
81
82
|
other_node = base[key]
|
82
|
-
other_node && node.value == other_node.value && !ignore_key?(key, :eq_base, locale)
|
83
|
+
other_node && !node.reference? && node.value == other_node.value && !ignore_key?(key, :eq_base, locale)
|
83
84
|
}.set_root_key!(locale, type: :eq_base)
|
84
85
|
end
|
85
86
|
|
@@ -0,0 +1,68 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module I18n::Tasks
|
3
|
+
module References
|
4
|
+
# Given a raw usage tree and a tree of reference keys in the data, return 3 trees:
|
5
|
+
# 1. Raw references -- a subset of the usages tree with keys that are reference key usages.
|
6
|
+
# 2. Resolved references -- all the used references in their fully resolved form.
|
7
|
+
# 3. Reference keys -- all the used reference keys.
|
8
|
+
def process_references(usages, data_references = merge_reference_trees(data_forest.select_keys { |_, node| node.reference? }))
|
9
|
+
fail ArgumentError.new('usages must be a Data::Tree::Instance') unless usages.is_a?(Data::Tree::Siblings)
|
10
|
+
fail ArgumentError.new('all_references must be a Data::Tree::Instance') unless data_references.is_a?(Data::Tree::Siblings)
|
11
|
+
raw_refs = empty_forest
|
12
|
+
resolved_refs = empty_forest
|
13
|
+
refs = empty_forest
|
14
|
+
data_references.key_to_node.each do |ref_key_part, ref_node|
|
15
|
+
usages.each do |usage_node|
|
16
|
+
next unless usage_node.key == ref_key_part
|
17
|
+
if ref_node.leaf?
|
18
|
+
unless refs.key_to_node.key?(ref_node.key)
|
19
|
+
refs.merge_node!(Data::Tree::Node.new(key: ref_node.key, data: usage_node.data))
|
20
|
+
end
|
21
|
+
resolved_refs.merge!(
|
22
|
+
Data::Tree::Siblings.from_key_names([ref_node.value.to_s]) { |_, resolved_node|
|
23
|
+
raw_refs.merge_node!(usage_node)
|
24
|
+
if usage_node.leaf?
|
25
|
+
resolved_node.data.merge!(usage_node.data)
|
26
|
+
else
|
27
|
+
resolved_node.children = usage_node.children
|
28
|
+
end
|
29
|
+
}.tap { |new_resolved_refs|
|
30
|
+
refs.key_to_node[ref_node.key].data.tap { |ref_data|
|
31
|
+
ref_data[:occurrences] ||= []
|
32
|
+
new_resolved_refs.leaves { |leaf| ref_data[:occurrences].concat(leaf.data[:occurrences] || []) }
|
33
|
+
ref_data[:occurrences].sort_by!(&:path)
|
34
|
+
ref_data[:occurrences].uniq!
|
35
|
+
}
|
36
|
+
})
|
37
|
+
else
|
38
|
+
child_raw_refs, child_resolved_refs, child_refs = process_references(usage_node.children, ref_node.children)
|
39
|
+
raw_refs.merge_node! Data::Tree::Node.new(key: ref_node.key, children: child_raw_refs) unless child_raw_refs.empty?
|
40
|
+
resolved_refs.merge! child_resolved_refs
|
41
|
+
refs.merge_node! Data::Tree::Node.new(key: ref_node.key, children: child_refs) unless child_refs.empty?
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
[raw_refs, resolved_refs, refs]
|
46
|
+
end
|
47
|
+
|
48
|
+
# Given a forest of references, merge trees into one tree, ensuring there are no conflicting references.
|
49
|
+
# @param roots [Data::Tree::Siblings]
|
50
|
+
# @return [Data::Tree::Siblings]
|
51
|
+
def merge_reference_trees(roots)
|
52
|
+
roots.inject(empty_forest) do |forest, root|
|
53
|
+
root.keys { |full_key, node|
|
54
|
+
log_warn(
|
55
|
+
"Self-referencing key #{node.full_key(root: false).inspect} in #{node.data[:locale].inspect}"
|
56
|
+
) if full_key == node.value.to_s
|
57
|
+
}
|
58
|
+
forest.merge!(
|
59
|
+
root.children,
|
60
|
+
on_leaves_merge: -> (node, other) {
|
61
|
+
log_warn(
|
62
|
+
"Conflicting references: #{node.full_key(root: false)} ⮕ #{node.value} in #{node.data[:locale]}, but ⮕ #{other.value} in #{other.data[:locale]}"
|
63
|
+
) if node.value != other.value
|
64
|
+
})
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
@@ -1,3 +1,4 @@
|
|
1
|
+
# frozen_string_literal: true
|
1
2
|
module I18n::Tasks::Reports
|
2
3
|
class Base
|
3
4
|
include I18n::Tasks::Logging
|
@@ -27,11 +28,9 @@ module I18n::Tasks::Reports
|
|
27
28
|
"Same value as #{locale} (#{key_values.count || '∅'})"
|
28
29
|
end
|
29
30
|
|
30
|
-
def used_title(
|
31
|
-
|
32
|
-
filter
|
33
|
-
used_n = leaves.map { |node| node.data[:occurrences].size }.reduce(:+).to_i
|
34
|
-
"#{leaves.length} key#{'s' if leaves.size != 1}#{" matching '#{filter}'" if filter}#{" (#{used_n} usage#{'s' if used_n != 1})" if used_n > 0}"
|
31
|
+
def used_title(keys_nodes, filter)
|
32
|
+
used_n = keys_nodes.map { |_k, node| node.data[:occurrences].size }.reduce(:+).to_i
|
33
|
+
"#{keys_nodes.size} key#{'s' if keys_nodes.size != 1}#{" matching '#{filter}'" if filter}#{" (#{used_n} usage#{'s' if used_n != 1})" if used_n > 0}"
|
35
34
|
end
|
36
35
|
|
37
36
|
# Sort keys by their attributes in order
|
@@ -1,3 +1,4 @@
|
|
1
|
+
# frozen_string_literal: true
|
1
2
|
require 'i18n/tasks/reports/base'
|
2
3
|
require 'terminal-table'
|
3
4
|
module I18n
|
@@ -29,8 +30,12 @@ module I18n
|
|
29
30
|
end
|
30
31
|
|
31
32
|
def used_keys(used_tree = task.used_tree)
|
32
|
-
|
33
|
-
keys_nodes = used_tree.
|
33
|
+
# For the used tree we may have usage nodes that are not leaves as references.
|
34
|
+
keys_nodes = used_tree.nodes.select { |node| !!node.data[:occurrences] }.map { |node|
|
35
|
+
[node.full_key(root: false), node]
|
36
|
+
}
|
37
|
+
print_title used_title(keys_nodes, used_tree.first.root.data[:key_filter])
|
38
|
+
# Group multiple nodes
|
34
39
|
if keys_nodes.present?
|
35
40
|
keys_nodes.sort! { |a, b| a[0] <=> b[0] }.each do |key, node|
|
36
41
|
print_occurrences node, key
|
@@ -80,13 +85,31 @@ module I18n
|
|
80
85
|
if leaf[:type] == :missing_used
|
81
86
|
first_occurrence leaf
|
82
87
|
else
|
83
|
-
"#{cyan leaf[:data][:missing_diff_locale]} #{leaf[:value].
|
88
|
+
"#{cyan leaf[:data][:missing_diff_locale]} #{format_value(leaf[:value].is_a?(String) ? leaf[:value].strip : leaf[:value])}"
|
84
89
|
end
|
85
90
|
end
|
86
91
|
|
92
|
+
def format_value(val)
|
93
|
+
val.is_a?(Symbol) ? "#{bold(yellow('⮕ '))}#{yellow(val.to_s)}" : val.to_s.strip
|
94
|
+
end
|
95
|
+
|
96
|
+
def format_reference_desc(node)
|
97
|
+
case node.data[:type]
|
98
|
+
when :reference_usage
|
99
|
+
bold(yellow('(reference)'))
|
100
|
+
when :reference_usage_resolved
|
101
|
+
bold(yellow('(resolved reference)'))
|
102
|
+
when :reference_usage_key
|
103
|
+
bold(yellow('(reference key)'))
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
87
107
|
def print_occurrences(node, full_key = node.full_key)
|
88
108
|
occurrences = node.data[:occurrences]
|
89
|
-
puts
|
109
|
+
puts [bold("#{full_key}"),
|
110
|
+
format_reference_desc(node),
|
111
|
+
(green(occurrences.size.to_s) if occurrences.size > 1)
|
112
|
+
].compact.join ' '
|
90
113
|
occurrences.each do |occurrence|
|
91
114
|
puts " #{key_occurrence full_key, occurrence}"
|
92
115
|
end
|
@@ -98,7 +121,7 @@ module I18n
|
|
98
121
|
bold(cyan(I18n.t('i18n_tasks.common.key'))),
|
99
122
|
I18n.t('i18n_tasks.common.value')] do |t|
|
100
123
|
t.rows = locale_key_values.map { |(locale, k, v)|
|
101
|
-
[{value: cyan(locale), alignment: :center}, cyan(k), v
|
124
|
+
[{value: cyan(locale), alignment: :center}, cyan(k), format_value(v)]
|
102
125
|
}
|
103
126
|
end
|
104
127
|
else
|
@@ -133,7 +156,7 @@ module I18n
|
|
133
156
|
|
134
157
|
def key_occurrence(full_key, occurrence)
|
135
158
|
location = green "#{occurrence.path}:#{occurrence.line_num}"
|
136
|
-
source = highlight_key(full_key, occurrence.line, occurrence.line_pos..-1).strip
|
159
|
+
source = highlight_key(occurrence.raw_key || full_key, occurrence.line, occurrence.line_pos..-1).strip
|
137
160
|
"#{location} #{source}"
|
138
161
|
end
|
139
162
|
|
@@ -1,3 +1,4 @@
|
|
1
|
+
# frozen_string_literal: true
|
1
2
|
module I18n
|
2
3
|
module Tasks
|
3
4
|
module Scanners
|
@@ -9,7 +10,7 @@ module I18n
|
|
9
10
|
# @param contents [String] contents of the file at the path.
|
10
11
|
# @param position [Fixnum] position just before the beginning of the match.
|
11
12
|
# @return [Results::Occurrence]
|
12
|
-
def occurrence_from_position(path, contents, position)
|
13
|
+
def occurrence_from_position(path, contents, position, raw_key: nil)
|
13
14
|
line_begin = contents.rindex(/^/, position - 1)
|
14
15
|
line_end = contents.index(/.(?=\r?\n|$)/, position)
|
15
16
|
Results::Occurrence.new(
|
@@ -17,7 +18,8 @@ module I18n
|
|
17
18
|
pos: position,
|
18
19
|
line_num: contents[0..position].count("\n".freeze) + 1,
|
19
20
|
line_pos: position - line_begin + 1,
|
20
|
-
line: contents[line_begin..line_end]
|
21
|
+
line: contents[line_begin..line_end],
|
22
|
+
raw_key: raw_key)
|
21
23
|
end
|
22
24
|
end
|
23
25
|
end
|
@@ -1,3 +1,4 @@
|
|
1
|
+
# frozen_string_literal: true
|
1
2
|
require 'i18n/tasks/scanners/file_scanner'
|
2
3
|
require 'i18n/tasks/scanners/relative_keys'
|
3
4
|
require 'i18n/tasks/scanners/occurrence_from_position'
|
@@ -17,13 +18,13 @@ module I18n::Tasks::Scanners
|
|
17
18
|
protected
|
18
19
|
|
19
20
|
# Extract i18n keys from file based on the pattern which must capture the key literal.
|
20
|
-
# @return [Array<[key, Results::
|
21
|
+
# @return [Array<[key, Results::Occurrence]>] each occurrence found in the file
|
21
22
|
def scan_file(path)
|
22
23
|
keys = []
|
23
24
|
text = read_file(path)
|
24
25
|
text.scan(@pattern) do |match|
|
25
26
|
src_pos = Regexp.last_match.offset(0).first
|
26
|
-
location = occurrence_from_position(path, text, src_pos)
|
27
|
+
location = occurrence_from_position(path, text, src_pos, raw_key: strip_literal(match[0]))
|
27
28
|
next if exclude_line?(location.line, path)
|
28
29
|
key = match_to_key(match, path, location)
|
29
30
|
next unless key
|
@@ -1,3 +1,4 @@
|
|
1
|
+
# frozen_string_literal: true
|
1
2
|
module I18n::Tasks
|
2
3
|
module Scanners
|
3
4
|
module Results
|
@@ -23,27 +24,33 @@ module I18n::Tasks
|
|
23
24
|
# @return [String, nil] the value of the `default:` argument of the translate call.
|
24
25
|
attr_reader :default_arg
|
25
26
|
|
27
|
+
# @return [String, nil] the raw key (for relative keys and references)
|
28
|
+
attr_accessor :raw_key
|
29
|
+
|
26
30
|
# @param path [String]
|
27
31
|
# @param pos [Fixnum]
|
28
32
|
# @param line_num [Fixnum]
|
29
33
|
# @param line_pos [Fixnum]
|
30
34
|
# @param line [String]
|
35
|
+
# @param raw_key [String, nil]
|
31
36
|
# @param default_arg [String, nil]
|
32
|
-
def initialize(path:, pos:, line_num:, line_pos:, line:, default_arg: nil)
|
37
|
+
def initialize(path:, pos:, line_num:, line_pos:, line:, raw_key: nil, default_arg: nil)
|
33
38
|
@path = path
|
34
39
|
@pos = pos
|
35
40
|
@line_num = line_num
|
36
41
|
@line_pos = line_pos
|
37
42
|
@line = line
|
43
|
+
@raw_key = raw_key
|
38
44
|
@default_arg = default_arg
|
39
45
|
end
|
40
46
|
|
41
47
|
def inspect
|
42
|
-
"Occurrence(#{@path}:#{@line_num}:#{@line_pos}
|
48
|
+
"Occurrence(#{@path}:#{@line_num}:#{@line_pos}:#{@pos}:#{@raw_key}:#{@default_arg})"
|
43
49
|
end
|
44
50
|
|
45
51
|
def ==(other)
|
46
|
-
other.path == @path && other.pos == @pos && other.line_num == @line_num && other.line == @line
|
52
|
+
other.path == @path && other.pos == @pos && other.line_num == @line_num && other.line == @line &&
|
53
|
+
other.raw_key == @raw_key && other.default_arg == @default_arg
|
47
54
|
end
|
48
55
|
|
49
56
|
def eql?(other)
|
@@ -1,3 +1,4 @@
|
|
1
|
+
# frozen_string_literal: true
|
1
2
|
require 'i18n/tasks/scanners/file_scanner'
|
2
3
|
require 'i18n/tasks/scanners/relative_keys'
|
3
4
|
require 'i18n/tasks/scanners/ruby_ast_call_finder'
|
@@ -75,7 +76,7 @@ module I18n::Tasks::Scanners
|
|
75
76
|
else
|
76
77
|
key
|
77
78
|
end
|
78
|
-
[full_key, range_to_occurrence(location.expression, default_arg: default_arg)]
|
79
|
+
[full_key, range_to_occurrence(key, location.expression, default_arg: default_arg)]
|
79
80
|
end
|
80
81
|
end
|
81
82
|
|
@@ -163,16 +164,18 @@ module I18n::Tasks::Scanners
|
|
163
164
|
/controllers|mailers/.match(path)
|
164
165
|
end
|
165
166
|
|
167
|
+
# @param raw_key [String]
|
166
168
|
# @param range [Parser::Source::Range]
|
167
169
|
# @param default_arg [String, nil]
|
168
170
|
# @return [Results::Occurrence]
|
169
|
-
def range_to_occurrence(range, default_arg: nil)
|
171
|
+
def range_to_occurrence(raw_key, range, default_arg: nil)
|
170
172
|
Results::Occurrence.new(
|
171
173
|
path: range.source_buffer.name,
|
172
174
|
pos: range.begin_pos,
|
173
175
|
line_num: range.line,
|
174
176
|
line_pos: range.column,
|
175
177
|
line: range.source_line,
|
178
|
+
raw_key: raw_key,
|
176
179
|
default_arg: default_arg)
|
177
180
|
end
|
178
181
|
|
data/lib/i18n/tasks/split_key.rb
CHANGED
data/lib/i18n/tasks/stats.rb
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
# frozen_string_literal: true
|
1
2
|
require 'set'
|
2
3
|
|
3
4
|
module I18n
|
@@ -11,10 +12,13 @@ module I18n
|
|
11
12
|
# @param [String] locale
|
12
13
|
# @param [Boolean] strict if true, do not match dynamic keys
|
13
14
|
def unused_tree(locale: base_locale, strict: nil)
|
15
|
+
used_key_names = used_tree(strict: true).keys.reject {|_key, node|
|
16
|
+
node.data[:type] == :used_reference_key_raw
|
17
|
+
}.map(&:first)
|
14
18
|
collapse_plural_nodes! data[locale].select_keys { |key, _node|
|
15
19
|
!ignore_key?(key, :unused) &&
|
16
20
|
(strict || !used_in_expr?(key)) &&
|
17
|
-
!
|
21
|
+
!used_key_names.include?(depluralize_key(key, locale))
|
18
22
|
}
|
19
23
|
end
|
20
24
|
end
|
data/lib/i18n/tasks/used_keys.rb
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
# frozen_string_literal: true
|
1
2
|
require 'find'
|
2
3
|
require 'i18n/tasks/scanners/pattern_with_scope_scanner'
|
3
4
|
require 'i18n/tasks/scanners/ruby_ast_scanner'
|
@@ -30,8 +31,26 @@ module I18n::Tasks
|
|
30
31
|
# @param key_filter [String] only return keys matching this pattern.
|
31
32
|
# @param strict [Boolean] if true, dynamic keys are excluded (e.g. `t("category.#{ category.key }")`)
|
32
33
|
# @return [Data::Tree::Siblings]
|
33
|
-
def used_tree(key_filter: nil, strict: nil)
|
34
|
-
|
34
|
+
def used_tree(key_filter: nil, strict: nil, include_raw_references: false)
|
35
|
+
src_tree = used_in_source_tree(key_filter: key_filter, strict: strict)
|
36
|
+
|
37
|
+
raw_refs, resolved_refs, used_refs = process_references(src_tree['used'].children)
|
38
|
+
raw_refs.leaves { |node| node.data[:type] = :reference_usage }
|
39
|
+
resolved_refs.leaves { |node| node.data[:type] = :reference_usage_resolved }
|
40
|
+
used_refs.leaves { |node| node.data[:type] = :reference_usage_key }
|
41
|
+
src_tree.tap do |result|
|
42
|
+
tree = result['used'].children
|
43
|
+
tree.subtract_by_key!(raw_refs)
|
44
|
+
if include_raw_references
|
45
|
+
tree.merge!(raw_refs)
|
46
|
+
end
|
47
|
+
tree.merge!(used_refs).merge!(resolved_refs)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def used_in_source_tree(key_filter: nil, strict: nil)
|
52
|
+
keys = ((@keys_used_in_source_tree ||= {})[strict?(strict)] ||=
|
53
|
+
scanner(strict: strict).keys.freeze)
|
35
54
|
if key_filter
|
36
55
|
key_filter_re = compile_key_pattern(key_filter)
|
37
56
|
keys = keys.reject { |k| k.key !~ key_filter_re }
|
@@ -43,6 +62,7 @@ module I18n::Tasks
|
|
43
62
|
).to_siblings
|
44
63
|
end
|
45
64
|
|
65
|
+
|
46
66
|
def scanner(strict: nil)
|
47
67
|
(@scanner ||= {})[strict?(strict)] ||= begin
|
48
68
|
shared_options = search_config.dup
|
@@ -102,20 +122,13 @@ module I18n::Tasks
|
|
102
122
|
@caching_file_reader ||= Scanners::Files::CachingFileReader.new
|
103
123
|
end
|
104
124
|
|
105
|
-
def used_key_names(strict: nil)
|
106
|
-
(@used_key_names ||= {})[strict?(strict)] ||= used_tree(strict: strict).key_names
|
107
|
-
end
|
108
|
-
|
109
|
-
# whether the key is used in the source
|
110
|
-
def used_key?(key)
|
111
|
-
used_key_names(strict: true).include?(key)
|
112
|
-
end
|
113
|
-
|
114
125
|
# @return whether the key is potentially used in a code expression such as `t("category.#{ category_key }")`
|
115
126
|
def used_in_expr?(key)
|
116
127
|
!!(key =~ expr_key_re)
|
117
128
|
end
|
118
129
|
|
130
|
+
private
|
131
|
+
|
119
132
|
# @param strict [Boolean, nil]
|
120
133
|
# @return [Boolean]
|
121
134
|
def strict?(strict)
|
@@ -123,16 +136,43 @@ module I18n::Tasks
|
|
123
136
|
end
|
124
137
|
|
125
138
|
# keys in the source that end with a ., e.g. t("category.#{ cat.i18n_key }") or t("category." + category.key)
|
126
|
-
|
139
|
+
# @param [String] replacement for interpolated values.
|
140
|
+
def expr_key_re(replacement: ':'.freeze)
|
127
141
|
@expr_key_re ||= begin
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
142
|
+
# disallow patterns with no keys
|
143
|
+
ignore_pattern_re = /\A[\.#{replacement}]*\z/
|
144
|
+
patterns = used_in_source_tree(strict: false).key_names.select { |k|
|
145
|
+
k.end_with?('.'.freeze) || k =~ /\#{/.freeze
|
146
|
+
}.map { |k|
|
147
|
+
pattern = "#{replace_key_exp(k, replacement)}#{replacement if k.end_with?('.'.freeze)}"
|
148
|
+
next if pattern =~ ignore_pattern_re
|
132
149
|
pattern
|
133
150
|
}.compact
|
134
151
|
compile_key_pattern "{#{patterns * ','}}"
|
135
152
|
end
|
136
153
|
end
|
154
|
+
|
155
|
+
# Replace interpolations in dynamic keys such as "category.#{category.i18n_key}".
|
156
|
+
# @param key [String]
|
157
|
+
# @param replacement [String]
|
158
|
+
# @return [String]
|
159
|
+
def replace_key_exp(key, replacement)
|
160
|
+
scanner = StringScanner.new(key)
|
161
|
+
braces = []
|
162
|
+
result = []
|
163
|
+
while (match_until = scanner.scan_until(/(?:#?\{|})/.freeze))
|
164
|
+
if scanner.matched == '#{'.freeze
|
165
|
+
braces << scanner.matched
|
166
|
+
result << match_until[0..-3] if braces.length == 1
|
167
|
+
elsif scanner.matched == '}'
|
168
|
+
prev_brace = braces.pop
|
169
|
+
result << replacement if braces.empty? && prev_brace == '#{'.freeze
|
170
|
+
else
|
171
|
+
braces << '{'.freeze
|
172
|
+
end
|
173
|
+
end
|
174
|
+
result << key[scanner.pos..-1] unless scanner.eos?
|
175
|
+
result.join
|
176
|
+
end
|
137
177
|
end
|
138
178
|
end
|
data/lib/i18n/tasks/version.rb
CHANGED
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.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- glebm
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2016-02-04 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activesupport
|
@@ -270,6 +270,7 @@ files:
|
|
270
270
|
- lib/i18n/tasks/logging.rb
|
271
271
|
- lib/i18n/tasks/missing_keys.rb
|
272
272
|
- lib/i18n/tasks/plural_keys.rb
|
273
|
+
- lib/i18n/tasks/references.rb
|
273
274
|
- lib/i18n/tasks/reports/base.rb
|
274
275
|
- lib/i18n/tasks/reports/spreadsheet.rb
|
275
276
|
- lib/i18n/tasks/reports/terminal.rb
|
@@ -322,7 +323,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
322
323
|
version: '0'
|
323
324
|
requirements: []
|
324
325
|
rubyforge_project:
|
325
|
-
rubygems_version: 2.
|
326
|
+
rubygems_version: 2.5.1
|
326
327
|
signing_key:
|
327
328
|
specification_version: 4
|
328
329
|
summary: Manage localization and translation with the awesome power of static analysis
|