i18n-tasks 1.0.15 → 1.1.0
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/README.md +85 -13
- data/Rakefile +4 -4
- data/bin/i18n-tasks +3 -3
- data/config/locales/en.yml +6 -0
- data/config/locales/ru.yml +7 -0
- data/i18n-tasks.gemspec +28 -41
- data/lib/i18n/tasks/base_task.rb +19 -19
- data/lib/i18n/tasks/cli.rb +37 -30
- data/lib/i18n/tasks/command/collection.rb +4 -4
- data/lib/i18n/tasks/command/commander.rb +5 -5
- data/lib/i18n/tasks/command/commands/check_prism.rb +126 -0
- data/lib/i18n/tasks/command/commands/data.rb +33 -33
- data/lib/i18n/tasks/command/commands/eq_base.rb +3 -3
- data/lib/i18n/tasks/command/commands/health.rb +6 -5
- data/lib/i18n/tasks/command/commands/interpolations.rb +14 -3
- data/lib/i18n/tasks/command/commands/meta.rb +6 -6
- data/lib/i18n/tasks/command/commands/missing.rb +25 -25
- data/lib/i18n/tasks/command/commands/tree.rb +33 -33
- data/lib/i18n/tasks/command/commands/usages.rb +24 -24
- data/lib/i18n/tasks/command/dsl.rb +1 -1
- data/lib/i18n/tasks/command/option_parsers/enum.rb +5 -5
- data/lib/i18n/tasks/command/option_parsers/locale.rb +4 -4
- data/lib/i18n/tasks/command/options/common.rb +16 -16
- data/lib/i18n/tasks/command/options/data.rb +18 -18
- data/lib/i18n/tasks/command/options/locales.rb +32 -32
- data/lib/i18n/tasks/commands.rb +14 -12
- data/lib/i18n/tasks/concurrent/cache.rb +1 -1
- data/lib/i18n/tasks/concurrent/cached_value.rb +1 -1
- data/lib/i18n/tasks/configuration.rb +22 -21
- data/lib/i18n/tasks/console_context.rb +11 -11
- data/lib/i18n/tasks/data/adapter/json_adapter.rb +1 -1
- data/lib/i18n/tasks/data/adapter/yaml_adapter.rb +5 -5
- data/lib/i18n/tasks/data/file_formats.rb +3 -3
- data/lib/i18n/tasks/data/file_system.rb +5 -5
- data/lib/i18n/tasks/data/file_system_base.rb +26 -26
- data/lib/i18n/tasks/data/language_names.rb +202 -0
- data/lib/i18n/tasks/data/router/conservative_router.rb +3 -3
- data/lib/i18n/tasks/data/router/isolating_router.rb +19 -19
- data/lib/i18n/tasks/data/router/pattern_router.rb +5 -5
- data/lib/i18n/tasks/data/tree/node.rb +27 -27
- data/lib/i18n/tasks/data/tree/nodes.rb +10 -10
- data/lib/i18n/tasks/data/tree/siblings.rb +20 -20
- data/lib/i18n/tasks/data/tree/traversal.rb +5 -5
- data/lib/i18n/tasks/data.rb +4 -4
- data/lib/i18n/tasks/html_keys.rb +2 -2
- data/lib/i18n/tasks/ignore_keys.rb +9 -9
- data/lib/i18n/tasks/interpolations.rb +21 -1
- data/lib/i18n/tasks/key_pattern_matching.rb +8 -8
- data/lib/i18n/tasks/logging.rb +2 -1
- data/lib/i18n/tasks/missing_keys.rb +24 -8
- data/lib/i18n/tasks/plural_keys.rb +6 -4
- data/lib/i18n/tasks/references.rb +4 -4
- data/lib/i18n/tasks/reports/base.rb +18 -14
- data/lib/i18n/tasks/reports/terminal.rb +64 -47
- data/lib/i18n/tasks/scanners/ast_matchers/base_matcher.rb +3 -3
- data/lib/i18n/tasks/scanners/ast_matchers/default_i18n_subject_matcher.rb +3 -3
- data/lib/i18n/tasks/scanners/ast_matchers/message_receivers_matcher.rb +10 -10
- data/lib/i18n/tasks/scanners/ast_matchers/rails_model_matcher.rb +1 -1
- data/lib/i18n/tasks/scanners/erb_ast_scanner.rb +69 -10
- data/lib/i18n/tasks/scanners/file_scanner.rb +5 -5
- data/lib/i18n/tasks/scanners/files/caching_file_finder.rb +3 -3
- data/lib/i18n/tasks/scanners/files/caching_file_finder_provider.rb +3 -3
- data/lib/i18n/tasks/scanners/files/caching_file_reader.rb +2 -2
- data/lib/i18n/tasks/scanners/files/file_finder.rb +8 -8
- data/lib/i18n/tasks/scanners/files/file_reader.rb +1 -1
- data/lib/i18n/tasks/scanners/local_ruby_parser.rb +8 -8
- data/lib/i18n/tasks/scanners/occurrence_from_position.rb +1 -1
- data/lib/i18n/tasks/scanners/pattern_mapper.rb +7 -7
- data/lib/i18n/tasks/scanners/pattern_scanner.rb +20 -20
- data/lib/i18n/tasks/scanners/pattern_with_scope_scanner.rb +8 -8
- data/lib/i18n/tasks/scanners/prism_scanners/arguments_visitor.rb +8 -1
- data/lib/i18n/tasks/scanners/prism_scanners/nodes.rb +101 -61
- data/lib/i18n/tasks/scanners/prism_scanners/visitor.rb +169 -105
- data/lib/i18n/tasks/scanners/relative_keys.rb +8 -8
- data/lib/i18n/tasks/scanners/results/key_occurrences.rb +3 -3
- data/lib/i18n/tasks/scanners/results/occurrence.rb +14 -10
- data/lib/i18n/tasks/scanners/ruby_ast_call_finder.rb +1 -1
- data/lib/i18n/tasks/scanners/ruby_key_literals.rb +6 -6
- data/lib/i18n/tasks/scanners/ruby_parser_factory.rb +1 -1
- data/lib/i18n/tasks/scanners/ruby_scanner.rb +225 -0
- data/lib/i18n/tasks/scanners/scanner.rb +2 -2
- data/lib/i18n/tasks/scanners/scanner_multiplexer.rb +1 -1
- data/lib/i18n/tasks/split_key.rb +4 -4
- data/lib/i18n/tasks/stats.rb +3 -3
- data/lib/i18n/tasks/translation.rb +5 -5
- data/lib/i18n/tasks/translators/base_translator.rb +40 -14
- data/lib/i18n/tasks/translators/deepl_translator.rb +17 -14
- data/lib/i18n/tasks/translators/google_translator.rb +169 -25
- data/lib/i18n/tasks/translators/openai_translator.rb +34 -23
- data/lib/i18n/tasks/translators/watsonx_translator.rb +16 -16
- data/lib/i18n/tasks/translators/yandex_translator.rb +8 -8
- data/lib/i18n/tasks/unused_keys.rb +1 -1
- data/lib/i18n/tasks/used_keys.rb +32 -33
- data/lib/i18n/tasks/version.rb +1 -1
- data/lib/i18n/tasks.rb +17 -17
- data/templates/config/i18n-tasks.yml +12 -0
- data/templates/minitest/i18n_test.rb +3 -3
- data/templates/rspec/i18n_spec.rb +7 -7
- metadata +25 -185
- data/lib/i18n/tasks/scanners/prism_scanner.rb +0 -83
- data/lib/i18n/tasks/scanners/ruby_ast_scanner.rb +0 -145
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require
|
|
4
|
-
require
|
|
3
|
+
require "i18n/tasks/reports/base"
|
|
4
|
+
require "terminal-table"
|
|
5
5
|
module I18n
|
|
6
6
|
module Tasks
|
|
7
7
|
module Reports
|
|
@@ -10,17 +10,17 @@ module I18n
|
|
|
10
10
|
forest = collapse_missing_tree! forest
|
|
11
11
|
if forest.present?
|
|
12
12
|
print_title missing_title(forest)
|
|
13
|
-
print_table headings: [Rainbow(I18n.t(
|
|
14
|
-
|
|
15
|
-
|
|
13
|
+
print_table headings: [Rainbow(I18n.t("i18n_tasks.common.locale")).cyan.bright,
|
|
14
|
+
Rainbow(I18n.t("i18n_tasks.common.key")).cyan.bright,
|
|
15
|
+
I18n.t("i18n_tasks.missing.details_title")] do |t|
|
|
16
16
|
t.rows = sort_by_attr!(forest_to_attr(forest)).map do |a|
|
|
17
|
-
[{
|
|
18
|
-
|
|
19
|
-
|
|
17
|
+
[{value: Rainbow(format_locale(a[:locale])).cyan, alignment: :center},
|
|
18
|
+
format_key(a[:key], a[:data]),
|
|
19
|
+
missing_key_info(a)]
|
|
20
20
|
end
|
|
21
21
|
end
|
|
22
22
|
else
|
|
23
|
-
print_success I18n.t(
|
|
23
|
+
print_success I18n.t("i18n_tasks.missing.none")
|
|
24
24
|
end
|
|
25
25
|
end
|
|
26
26
|
|
|
@@ -29,7 +29,24 @@ module I18n
|
|
|
29
29
|
print_title inconsistent_interpolations_title(forest)
|
|
30
30
|
show_tree(forest)
|
|
31
31
|
else
|
|
32
|
-
print_success I18n.t(
|
|
32
|
+
print_success I18n.t("i18n_tasks.inconsistent_interpolations.none")
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def reserved_interpolations(forest = task.reserved_interpolations)
|
|
37
|
+
if forest.present?
|
|
38
|
+
print_title reserved_interpolations_title(forest)
|
|
39
|
+
print_table headings: [Rainbow(I18n.t("i18n_tasks.common.locale")).cyan.bright,
|
|
40
|
+
Rainbow(I18n.t("i18n_tasks.common.key")).cyan.bright,
|
|
41
|
+
I18n.t("i18n_tasks.reserved_interpolations.details_title")] do |t|
|
|
42
|
+
t.rows = sort_by_attr!(forest_to_attr(forest)).map do |a|
|
|
43
|
+
[{value: Rainbow(format_locale(a[:locale])).cyan, alignment: :center},
|
|
44
|
+
format_key(a[:key], a[:data]),
|
|
45
|
+
a[:value].join(", ")]
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
else
|
|
49
|
+
print_success I18n.t("i18n_tasks.reserved_interpolations.none")
|
|
33
50
|
end
|
|
34
51
|
end
|
|
35
52
|
|
|
@@ -45,7 +62,7 @@ module I18n
|
|
|
45
62
|
print_occurrences node, key
|
|
46
63
|
end
|
|
47
64
|
else
|
|
48
|
-
print_error I18n.t(
|
|
65
|
+
print_error I18n.t("i18n_tasks.usages.none")
|
|
49
66
|
end
|
|
50
67
|
end
|
|
51
68
|
|
|
@@ -55,7 +72,7 @@ module I18n
|
|
|
55
72
|
print_title unused_title(keys)
|
|
56
73
|
print_locale_key_value_data_table keys
|
|
57
74
|
else
|
|
58
|
-
print_success I18n.t(
|
|
75
|
+
print_success I18n.t("i18n_tasks.unused.none")
|
|
59
76
|
end
|
|
60
77
|
end
|
|
61
78
|
|
|
@@ -65,7 +82,7 @@ module I18n
|
|
|
65
82
|
print_title eq_base_title(keys)
|
|
66
83
|
print_locale_key_value_data_table keys
|
|
67
84
|
else
|
|
68
|
-
print_info Rainbow(
|
|
85
|
+
print_info Rainbow("No translations are the same as base value").cyan
|
|
69
86
|
end
|
|
70
87
|
end
|
|
71
88
|
|
|
@@ -74,39 +91,39 @@ module I18n
|
|
|
74
91
|
end
|
|
75
92
|
|
|
76
93
|
def forest_stats(forest, stats = task.forest_stats(forest))
|
|
77
|
-
text
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
title = Rainbow(I18n.t(
|
|
94
|
+
text = if stats[:locale_count] == 1
|
|
95
|
+
I18n.t("i18n_tasks.data_stats.text_single_locale", **stats)
|
|
96
|
+
else
|
|
97
|
+
I18n.t("i18n_tasks.data_stats.text", **stats)
|
|
98
|
+
end
|
|
99
|
+
title = Rainbow(I18n.t("i18n_tasks.data_stats.title", **stats.slice(:locales))).bright
|
|
83
100
|
print_info "#{Rainbow(title).cyan} #{Rainbow(text).cyan}"
|
|
84
101
|
end
|
|
85
102
|
|
|
86
103
|
def mv_results(results)
|
|
87
104
|
results.each do |(from, to)|
|
|
88
105
|
if to
|
|
89
|
-
print_info "#{Rainbow(from).cyan} #{Rainbow(
|
|
106
|
+
print_info "#{Rainbow(from).cyan} #{Rainbow("⮕").yellow.bright} #{Rainbow(to).cyan}"
|
|
90
107
|
else
|
|
91
|
-
print_info "#{Rainbow(from).red}#{Rainbow(
|
|
108
|
+
print_info "#{Rainbow(from).red}#{Rainbow(" 🗑").red.bright}"
|
|
92
109
|
end
|
|
93
110
|
end
|
|
94
111
|
end
|
|
95
112
|
|
|
96
113
|
def cp_results(results)
|
|
97
114
|
results.each do |(from, to)|
|
|
98
|
-
print_info "#{Rainbow(from).cyan} #{Rainbow(
|
|
115
|
+
print_info "#{Rainbow(from).cyan} #{Rainbow("+").yellow.bright} #{Rainbow(to).green}"
|
|
99
116
|
end
|
|
100
117
|
end
|
|
101
118
|
|
|
102
119
|
def check_normalized_results(non_normalized)
|
|
103
120
|
if non_normalized.empty?
|
|
104
|
-
print_success
|
|
121
|
+
print_success "All data is normalized"
|
|
105
122
|
return
|
|
106
123
|
end
|
|
107
|
-
log_stderr Rainbow(
|
|
124
|
+
log_stderr Rainbow("The following data requires normalization:").yellow
|
|
108
125
|
puts non_normalized
|
|
109
|
-
log_stderr Rainbow(
|
|
126
|
+
log_stderr Rainbow("Run `i18n-tasks normalize` to fix").yellow
|
|
110
127
|
end
|
|
111
128
|
|
|
112
129
|
private
|
|
@@ -116,7 +133,7 @@ module I18n
|
|
|
116
133
|
when :missing_used
|
|
117
134
|
first_occurrence leaf
|
|
118
135
|
when :missing_plural
|
|
119
|
-
leaf[:data][:missing_keys].join(
|
|
136
|
+
leaf[:data][:missing_keys].join(", ")
|
|
120
137
|
else
|
|
121
138
|
"#{Rainbow(leaf[:data][:missing_diff_locale]).cyan} " \
|
|
122
139
|
"#{format_value(leaf[:value].is_a?(String) ? leaf[:value].strip : leaf[:value])}"
|
|
@@ -127,16 +144,16 @@ module I18n
|
|
|
127
144
|
if data[:ref_info]
|
|
128
145
|
from, to = data[:ref_info]
|
|
129
146
|
resolved = key[0...to.length]
|
|
130
|
-
after
|
|
147
|
+
after = key[to.length..]
|
|
131
148
|
" #{Rainbow(from).yellow}#{Rainbow(after).cyan}\n" \
|
|
132
|
-
"#{Rainbow(
|
|
149
|
+
"#{Rainbow("⮕").yellow.bright} #{Rainbow(resolved).yellow.bright}"
|
|
133
150
|
else
|
|
134
151
|
Rainbow(key).cyan
|
|
135
152
|
end
|
|
136
153
|
end
|
|
137
154
|
|
|
138
155
|
def format_value(val)
|
|
139
|
-
val.is_a?(Symbol) ? "#{Rainbow(
|
|
156
|
+
val.is_a?(Symbol) ? "#{Rainbow("⮕ ").yellow.bright}#{Rainbow(val).yellow}" : val.to_s.strip
|
|
140
157
|
end
|
|
141
158
|
|
|
142
159
|
def format_reference_desc(node_data)
|
|
@@ -144,19 +161,19 @@ module I18n
|
|
|
144
161
|
|
|
145
162
|
case node_data[:ref_type]
|
|
146
163
|
when :reference_usage
|
|
147
|
-
Rainbow(
|
|
164
|
+
Rainbow("(ref)").yellow.bright
|
|
148
165
|
when :reference_usage_resolved
|
|
149
|
-
Rainbow(
|
|
166
|
+
Rainbow("(resolved ref)").yellow.bright
|
|
150
167
|
when :reference_usage_key
|
|
151
|
-
Rainbow(
|
|
168
|
+
Rainbow("(ref key)").yellow.bright
|
|
152
169
|
end
|
|
153
170
|
end
|
|
154
171
|
|
|
155
172
|
def print_occurrences(node, full_key = node.full_key)
|
|
156
173
|
occurrences = node.data[:occurrences]
|
|
157
174
|
puts [Rainbow(full_key).bright,
|
|
158
|
-
|
|
159
|
-
|
|
175
|
+
format_reference_desc(node.data),
|
|
176
|
+
(Rainbow(occurrences.size).green if occurrences.size > 1)].compact.join " "
|
|
160
177
|
occurrences.each do |occurrence|
|
|
161
178
|
puts " #{key_occurrence full_key, occurrence}"
|
|
162
179
|
end
|
|
@@ -164,25 +181,25 @@ module I18n
|
|
|
164
181
|
|
|
165
182
|
def print_locale_key_value_data_table(locale_key_value_datas)
|
|
166
183
|
if locale_key_value_datas.present?
|
|
167
|
-
print_table headings: [Rainbow(I18n.t(
|
|
168
|
-
|
|
169
|
-
|
|
184
|
+
print_table headings: [Rainbow(I18n.t("i18n_tasks.common.locale")).cyan.bright,
|
|
185
|
+
Rainbow(I18n.t("i18n_tasks.common.key")).cyan.bright,
|
|
186
|
+
I18n.t("i18n_tasks.common.value")] do |t|
|
|
170
187
|
t.rows = locale_key_value_datas.map do |(locale, k, v, data)|
|
|
171
|
-
[{
|
|
188
|
+
[{value: Rainbow(locale).cyan, alignment: :center}, format_key(k, data), format_value(v)]
|
|
172
189
|
end
|
|
173
190
|
end
|
|
174
191
|
else
|
|
175
|
-
puts
|
|
192
|
+
puts "ø"
|
|
176
193
|
end
|
|
177
194
|
end
|
|
178
195
|
|
|
179
196
|
def print_title(title)
|
|
180
|
-
log_stderr "#{Rainbow(title.strip).bright} #{Rainbow(
|
|
197
|
+
log_stderr "#{Rainbow(title.strip).bright} #{Rainbow("|").faint} " \
|
|
181
198
|
"#{"i18n-tasks v#{I18n::Tasks::VERSION}"}"
|
|
182
199
|
end
|
|
183
200
|
|
|
184
201
|
def print_success(message)
|
|
185
|
-
log_stderr Rainbow("✓ #{I18n.t(
|
|
202
|
+
log_stderr Rainbow("✓ #{I18n.t("i18n_tasks.cmd.encourage").sample} #{message}").green.bright
|
|
186
203
|
end
|
|
187
204
|
|
|
188
205
|
def print_error(message)
|
|
@@ -194,16 +211,16 @@ module I18n
|
|
|
194
211
|
end
|
|
195
212
|
|
|
196
213
|
def indent(txt, n = 2)
|
|
197
|
-
txt.gsub(/^/,
|
|
214
|
+
txt.gsub(/^/, " " * n)
|
|
198
215
|
end
|
|
199
216
|
|
|
200
|
-
def print_table(opts, &
|
|
201
|
-
puts ::Terminal::Table.new(opts, &
|
|
217
|
+
def print_table(opts, &)
|
|
218
|
+
puts ::Terminal::Table.new(opts, &)
|
|
202
219
|
end
|
|
203
220
|
|
|
204
221
|
def key_occurrence(full_key, occurrence)
|
|
205
222
|
location = Rainbow("#{occurrence.path}:#{occurrence.line_num}").green
|
|
206
|
-
source
|
|
223
|
+
source = highlight_key(occurrence.raw_key || full_key, occurrence.line, occurrence.line_pos..-1).strip
|
|
207
224
|
"#{location} #{source}"
|
|
208
225
|
end
|
|
209
226
|
|
|
@@ -214,8 +231,8 @@ module I18n
|
|
|
214
231
|
first = occurrences.first
|
|
215
232
|
[
|
|
216
233
|
Rainbow("#{first.path}:#{first.line_num}").green,
|
|
217
|
-
("(#{I18n.t
|
|
218
|
-
].compact.join(
|
|
234
|
+
("(#{I18n.t "i18n_tasks.common.n_more", count: occurrences.length - 1})" if occurrences.length > 1)
|
|
235
|
+
].compact.join(" ")
|
|
219
236
|
end
|
|
220
237
|
|
|
221
238
|
def highlight_key(full_key, line, range = (0..-1))
|
|
@@ -7,7 +7,7 @@ module I18n::Tasks::Scanners::AstMatchers
|
|
|
7
7
|
end
|
|
8
8
|
|
|
9
9
|
def convert_to_key_occurrences(send_node, _method_name, location: send_node.loc)
|
|
10
|
-
fail(
|
|
10
|
+
fail("Not implemented")
|
|
11
11
|
end
|
|
12
12
|
|
|
13
13
|
protected
|
|
@@ -32,7 +32,7 @@ module I18n::Tasks::Scanners::AstMatchers
|
|
|
32
32
|
elsif %i[true false].include?(node.type)
|
|
33
33
|
node.type.to_s
|
|
34
34
|
elsif node.type == :nil
|
|
35
|
-
|
|
35
|
+
""
|
|
36
36
|
elsif node.type == :array && array_join_with
|
|
37
37
|
extract_array_as_string(
|
|
38
38
|
node,
|
|
@@ -109,7 +109,7 @@ module I18n::Tasks::Scanners::AstMatchers
|
|
|
109
109
|
if array_reject_blank
|
|
110
110
|
children_strings.reject! do |x|
|
|
111
111
|
# empty strings and nils in the scope argument are ignored by i18n
|
|
112
|
-
x ==
|
|
112
|
+
x == ""
|
|
113
113
|
end
|
|
114
114
|
end
|
|
115
115
|
children_strings.join(array_join_with)
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require
|
|
4
|
-
require
|
|
3
|
+
require "i18n/tasks/scanners/ast_matchers/base_matcher"
|
|
4
|
+
require "i18n/tasks/scanners/results/occurrence"
|
|
5
5
|
|
|
6
6
|
module I18n::Tasks::Scanners::AstMatchers
|
|
7
7
|
class DefaultI18nSubjectMatcher < BaseMatcher
|
|
@@ -10,7 +10,7 @@ module I18n::Tasks::Scanners::AstMatchers
|
|
|
10
10
|
return unless children[1] == :default_i18n_subject
|
|
11
11
|
|
|
12
12
|
key = @scanner.absolute_key(
|
|
13
|
-
|
|
13
|
+
".subject",
|
|
14
14
|
location.expression.source_buffer.name,
|
|
15
15
|
calling_method: method_name
|
|
16
16
|
)
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require
|
|
4
|
-
require
|
|
3
|
+
require "i18n/tasks/scanners/ast_matchers/base_matcher"
|
|
4
|
+
require "i18n/tasks/scanners/results/occurrence"
|
|
5
5
|
|
|
6
6
|
module I18n::Tasks::Scanners::AstMatchers
|
|
7
7
|
class MessageReceiversMatcher < BaseMatcher
|
|
@@ -64,25 +64,25 @@ module I18n::Tasks::Scanners::AstMatchers
|
|
|
64
64
|
def process_options(node:, key:)
|
|
65
65
|
return [key, nil] if node&.type != :hash
|
|
66
66
|
|
|
67
|
-
scope_node = extract_hash_pair(node,
|
|
67
|
+
scope_node = extract_hash_pair(node, "scope")
|
|
68
68
|
|
|
69
69
|
if scope_node
|
|
70
70
|
scope = extract_string(
|
|
71
71
|
scope_node.children[1],
|
|
72
|
-
array_join_with:
|
|
72
|
+
array_join_with: ".",
|
|
73
73
|
array_flatten: true,
|
|
74
74
|
array_reject_blank: true
|
|
75
75
|
)
|
|
76
76
|
return nil if scope.nil? && scope_node.type != :nil
|
|
77
77
|
|
|
78
|
-
key = [scope, key].join(
|
|
78
|
+
key = [scope, key].join(".") unless scope == ""
|
|
79
79
|
end
|
|
80
|
-
if default_arg_node = extract_hash_pair(node,
|
|
80
|
+
if (default_arg_node = extract_hash_pair(node, "default"))
|
|
81
81
|
default_arg = if default_arg_node.children[1]&.type == :hash
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
82
|
+
extract_hash(default_arg_node.children[1])
|
|
83
|
+
else
|
|
84
|
+
extract_string(default_arg_node.children[1])
|
|
85
|
+
end
|
|
86
86
|
end
|
|
87
87
|
|
|
88
88
|
[key, default_arg]
|
|
@@ -1,16 +1,21 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require
|
|
4
|
-
require
|
|
3
|
+
require "i18n/tasks/scanners/ruby_scanner"
|
|
4
|
+
require "i18n/tasks/scanners/local_ruby_parser"
|
|
5
|
+
require "i18n/tasks/scanners/occurrence_from_position"
|
|
6
|
+
require "prism"
|
|
5
7
|
|
|
6
8
|
module I18n::Tasks::Scanners
|
|
7
|
-
# Scan for I18n.translate calls in ERB-file
|
|
8
|
-
class ErbAstScanner <
|
|
9
|
-
|
|
9
|
+
# Scan for I18n.translate calls in ERB-file using regexp and Parser/Prism
|
|
10
|
+
class ErbAstScanner < RubyScanner
|
|
11
|
+
include OccurrenceFromPosition
|
|
10
12
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
13
|
+
DEFAULT_REGEXP = /<%(={1,2}|-|\#-?|%)?(.*?)([-=])?%>/m
|
|
14
|
+
|
|
15
|
+
# Parser scanner, method called in RubyScanner
|
|
16
|
+
def ast_parser_parse_file(path)
|
|
17
|
+
@ruby_parser ||= LocalRubyParser.new(ignore_blocks: true)
|
|
18
|
+
super
|
|
14
19
|
end
|
|
15
20
|
|
|
16
21
|
private
|
|
@@ -34,11 +39,11 @@ module I18n::Tasks::Scanners
|
|
|
34
39
|
stop = match.end(0) - 2 - (tailch&.size || 0)
|
|
35
40
|
|
|
36
41
|
case character
|
|
37
|
-
when
|
|
42
|
+
when "=", nil, "-"
|
|
38
43
|
parsed, parsed_comments = handle_code(buffer, code, start, stop)
|
|
39
44
|
comments.concat(parsed_comments)
|
|
40
45
|
children << parsed unless parsed.nil?
|
|
41
|
-
when
|
|
46
|
+
when "#", "#-"
|
|
42
47
|
comments << handle_comment(buffer, start, stop)
|
|
43
48
|
end
|
|
44
49
|
end
|
|
@@ -74,5 +79,59 @@ module I18n::Tasks::Scanners
|
|
|
74
79
|
)
|
|
75
80
|
::Parser::AST::Node.new(:erb, children, location: location)
|
|
76
81
|
end
|
|
82
|
+
|
|
83
|
+
# Prism scanner, method called in RubyScanner
|
|
84
|
+
def prism_parse_file(path)
|
|
85
|
+
occurrences = []
|
|
86
|
+
content = File.read(path)
|
|
87
|
+
|
|
88
|
+
content.scan(DEFAULT_REGEXP) do |indicator, code, _tailch, _rspace|
|
|
89
|
+
match = Regexp.last_match
|
|
90
|
+
character = indicator ? indicator[0] : nil
|
|
91
|
+
start = match.begin(0) + 2 + (character&.size || 0)
|
|
92
|
+
|
|
93
|
+
case character
|
|
94
|
+
when "=", nil, "-"
|
|
95
|
+
occurrences += process_code(path, code, content, start)
|
|
96
|
+
when "#", "#-"
|
|
97
|
+
occurrences += process_comments(path, code, content, start)
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
occurrences
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def process_code(path, code, content, start)
|
|
105
|
+
return [] if code.strip.empty? # skip empty ERB tags
|
|
106
|
+
|
|
107
|
+
process_prism_results(path, Prism.parse(code)).map do |key, occurrence|
|
|
108
|
+
[
|
|
109
|
+
key,
|
|
110
|
+
occurrence_from_position(
|
|
111
|
+
path,
|
|
112
|
+
content,
|
|
113
|
+
start + occurrence.pos,
|
|
114
|
+
raw_key: occurrence.raw_key
|
|
115
|
+
)
|
|
116
|
+
]
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
def process_comments(path, code, content, start)
|
|
121
|
+
return [] if code.strip.empty?
|
|
122
|
+
|
|
123
|
+
parsed = Prism.parse(code.gsub("i18n-tasks-use ", "#i18n-tasks-use "))
|
|
124
|
+
process_prism_results(path, parsed).map do |key, occurrence|
|
|
125
|
+
[
|
|
126
|
+
key,
|
|
127
|
+
occurrence_from_position(
|
|
128
|
+
path,
|
|
129
|
+
content,
|
|
130
|
+
start + (code.index(key) || occurrence.pos),
|
|
131
|
+
raw_key: occurrence.raw_key
|
|
132
|
+
)
|
|
133
|
+
]
|
|
134
|
+
end
|
|
135
|
+
end
|
|
77
136
|
end
|
|
78
137
|
end
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require
|
|
3
|
+
require "i18n/tasks/scanners/scanner"
|
|
4
4
|
|
|
5
5
|
module I18n::Tasks::Scanners
|
|
6
6
|
# A base class for a scanner that analyses files.
|
|
@@ -16,7 +16,7 @@ module I18n::Tasks::Scanners
|
|
|
16
16
|
file_reader: Files::CachingFileReader.new
|
|
17
17
|
)
|
|
18
18
|
super()
|
|
19
|
-
@config
|
|
19
|
+
@config = config
|
|
20
20
|
@file_reader = file_reader
|
|
21
21
|
@file_finder = file_finder_provider.get(**config.slice(:paths, :only, :exclude))
|
|
22
22
|
end
|
|
@@ -36,7 +36,7 @@ module I18n::Tasks::Scanners
|
|
|
36
36
|
#
|
|
37
37
|
# @return [Array<[key, Results::KeyOccurrence]>] each occurrence found in the file
|
|
38
38
|
def scan_file(_path)
|
|
39
|
-
fail
|
|
39
|
+
fail "Unimplemented"
|
|
40
40
|
end
|
|
41
41
|
|
|
42
42
|
# Read a file. Reads of the same path are cached.
|
|
@@ -53,8 +53,8 @@ module I18n::Tasks::Scanners
|
|
|
53
53
|
# @param (see FileFinder#traverse_files)
|
|
54
54
|
# @yieldparam (see FileFinder#traverse_files)
|
|
55
55
|
# @return (see FileFinder#traverse_files)
|
|
56
|
-
def traverse_files(&
|
|
57
|
-
@file_finder.traverse_files(&
|
|
56
|
+
def traverse_files(&)
|
|
57
|
+
@file_finder.traverse_files(&)
|
|
58
58
|
end
|
|
59
59
|
|
|
60
60
|
# @note This method is cached, it will only access the filesystem on the first invocation.
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require
|
|
4
|
-
require
|
|
3
|
+
require "i18n/tasks/concurrent/cached_value"
|
|
4
|
+
require "i18n/tasks/scanners/files/file_finder"
|
|
5
5
|
|
|
6
6
|
module I18n::Tasks::Scanners::Files
|
|
7
7
|
# Finds the files in the specified search paths with support for exclusion / inclusion patterns.
|
|
@@ -23,7 +23,7 @@ module I18n::Tasks::Scanners::Files
|
|
|
23
23
|
# @yieldparam (see FileFinder#traverse_files)
|
|
24
24
|
# @return (see FileFinder#traverse_files)
|
|
25
25
|
|
|
26
|
-
|
|
26
|
+
alias_method :uncached_find_files, :find_files
|
|
27
27
|
private :uncached_find_files
|
|
28
28
|
|
|
29
29
|
# @note This method is cached, it will only access the filesystem on the first invocation.
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require
|
|
4
|
-
require
|
|
3
|
+
require "i18n/tasks/concurrent/cache"
|
|
4
|
+
require "i18n/tasks/scanners/files/caching_file_finder"
|
|
5
5
|
|
|
6
6
|
module I18n::Tasks::Scanners::Files
|
|
7
7
|
# Finds the files and provides their contents.
|
|
@@ -12,7 +12,7 @@ module I18n::Tasks::Scanners::Files
|
|
|
12
12
|
# @param exclude [Array<String>]
|
|
13
13
|
def initialize(exclude: [])
|
|
14
14
|
@cache = ::I18n::Tasks::Concurrent::Cache.new
|
|
15
|
-
@defaults = {
|
|
15
|
+
@defaults = {exclude: exclude}
|
|
16
16
|
end
|
|
17
17
|
|
|
18
18
|
# Initialize a {CachingFileFinder} or get one from cache based on the constructor arguments.
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require
|
|
4
|
-
require
|
|
3
|
+
require "i18n/tasks/concurrent/cache"
|
|
4
|
+
require "i18n/tasks/scanners/files/file_reader"
|
|
5
5
|
|
|
6
6
|
module I18n::Tasks::Scanners::Files
|
|
7
7
|
# Reads the files in 'rb' mode and UTF-8 encoding.
|
|
@@ -13,10 +13,10 @@ module I18n::Tasks::Scanners::Files
|
|
|
13
13
|
# Files not matching any of the inclusion patterns will be excluded.
|
|
14
14
|
# @param exclude [Arry<String>] {File.fnmatch}-compatible patterns of files to exclude.
|
|
15
15
|
# Files matching any of the exclusion patterns will be excluded even if they match an inclusion pattern.
|
|
16
|
-
def initialize(paths: [
|
|
17
|
-
fail
|
|
16
|
+
def initialize(paths: ["."], only: nil, exclude: [])
|
|
17
|
+
fail "paths argument is required" if paths.nil?
|
|
18
18
|
|
|
19
|
-
@paths
|
|
19
|
+
@paths = paths
|
|
20
20
|
@include = only
|
|
21
21
|
@exclude = exclude || []
|
|
22
22
|
end
|
|
@@ -26,8 +26,8 @@ module I18n::Tasks::Scanners::Files
|
|
|
26
26
|
# @yield [path]
|
|
27
27
|
# @yieldparam path [String] the path of the found file.
|
|
28
28
|
# @return [Array<of block results>]
|
|
29
|
-
def traverse_files(&
|
|
30
|
-
find_files.map(&
|
|
29
|
+
def traverse_files(&)
|
|
30
|
+
find_files.map(&)
|
|
31
31
|
end
|
|
32
32
|
|
|
33
33
|
# @return [Array<String>] found files
|
|
@@ -36,10 +36,10 @@ module I18n::Tasks::Scanners::Files
|
|
|
36
36
|
paths = @paths.select { |p| File.exist?(p) }
|
|
37
37
|
log_warn "None of the search.paths exist #{@paths.inspect}" if paths.empty?
|
|
38
38
|
Find.find(*paths) do |path|
|
|
39
|
-
is_dir
|
|
40
|
-
hidden
|
|
39
|
+
is_dir = File.directory?(path)
|
|
40
|
+
hidden = File.basename(path).start_with?(".") && !%w[. ./].include?(path)
|
|
41
41
|
not_incl = @include && !path_fnmatch_any?(path, @include)
|
|
42
|
-
excl
|
|
42
|
+
excl = path_fnmatch_any?(path, @exclude)
|
|
43
43
|
if is_dir || hidden || not_incl || excl
|
|
44
44
|
Find.prune if is_dir && (hidden || excl)
|
|
45
45
|
else
|
|
@@ -12,7 +12,7 @@ module I18n::Tasks::Scanners::Files
|
|
|
12
12
|
# @return [String] file contents
|
|
13
13
|
def read_file(path)
|
|
14
14
|
result = nil
|
|
15
|
-
File.open(path,
|
|
15
|
+
File.open(path, "rb", encoding: "UTF-8") { |f| result = f.read }
|
|
16
16
|
result
|
|
17
17
|
end
|
|
18
18
|
end
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require
|
|
3
|
+
require "i18n/tasks/scanners/ruby_parser_factory"
|
|
4
4
|
|
|
5
5
|
module I18n::Tasks::Scanners
|
|
6
6
|
class LocalRubyParser
|
|
7
7
|
# ignore_blocks feature inspired by shopify/better-html
|
|
8
8
|
# https://github.com/Shopify/better-html/blob/087943ffd2a5877fa977d71532010b0c91239519/lib/better_html/test_helper/ruby_node.rb#L24
|
|
9
|
-
BLOCK_EXPR = /\s*((\s+|\))do|\{)(\s*\|[^|]*\|)?\s*\Z
|
|
9
|
+
BLOCK_EXPR = /\s*((\s+|\))do|\{)(\s*\|[^|]*\|)?\s*\Z/
|
|
10
10
|
|
|
11
11
|
def initialize(ignore_blocks: false)
|
|
12
12
|
@parser = RubyParserFactory.create_parser
|
|
@@ -15,12 +15,12 @@ module I18n::Tasks::Scanners
|
|
|
15
15
|
|
|
16
16
|
# Parse string and normalize location
|
|
17
17
|
def parse(source, location: nil)
|
|
18
|
-
buffer = ::Parser::Source::Buffer.new(
|
|
18
|
+
buffer = ::Parser::Source::Buffer.new("(string)")
|
|
19
19
|
buffer.source = if @ignore_blocks
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
20
|
+
source.sub(BLOCK_EXPR, "")
|
|
21
|
+
else
|
|
22
|
+
source
|
|
23
|
+
end
|
|
24
24
|
|
|
25
25
|
@parser.reset
|
|
26
26
|
ast, comments = @parser.parse_with_comments(buffer)
|
|
@@ -42,7 +42,7 @@ module I18n::Tasks::Scanners
|
|
|
42
42
|
node.updated(
|
|
43
43
|
nil,
|
|
44
44
|
node.children.map { |child| normalize_location(child, location) },
|
|
45
|
-
{
|
|
45
|
+
{location: updated_location(location, node.location)}
|
|
46
46
|
)
|
|
47
47
|
end
|
|
48
48
|
|
|
@@ -13,7 +13,7 @@ module I18n
|
|
|
13
13
|
# @return [Results::Occurrence]
|
|
14
14
|
def occurrence_from_position(path, contents, position, raw_key: nil)
|
|
15
15
|
line_begin = contents.rindex(/^/, position - 1)
|
|
16
|
-
line_end
|
|
16
|
+
line_end = contents.index(/.(?=\r?\n|$)/, position)
|
|
17
17
|
Results::Occurrence.new(
|
|
18
18
|
path: path,
|
|
19
19
|
pos: position,
|