i18n-tasks 1.1.2 → 1.2.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 +321 -242
- data/Rakefile +5 -1
- data/benchmarks/bench_helper.rb +156 -0
- data/benchmarks/data_bench.rb +173 -0
- data/benchmarks/end_to_end_bench.rb +138 -0
- data/benchmarks/fixtures/generator.rb +257 -0
- data/benchmarks/results/baseline.json +250 -0
- data/benchmarks/run_all.rb +60 -0
- data/benchmarks/scanning_bench.rb +135 -0
- data/benchmarks/tree_bench.rb +205 -0
- data/config/locales/en.yml +10 -0
- data/config/locales/ru.yml +14 -0
- data/i18n-tasks.gemspec +1 -1
- data/lib/i18n/tasks/base_task.rb +2 -0
- data/lib/i18n/tasks/command/commands/check_prism.rb +67 -15
- data/lib/i18n/tasks/command/commands/prune.rb +57 -0
- data/lib/i18n/tasks/commands.rb +2 -0
- data/lib/i18n/tasks/data/adapter/json_adapter.rb +1 -1
- data/lib/i18n/tasks/data/file_formats.rb +4 -1
- data/lib/i18n/tasks/data/router/conservative_router.rb +13 -22
- data/lib/i18n/tasks/data/router/pattern_router.rb +26 -14
- data/lib/i18n/tasks/data/tree/node.rb +3 -3
- data/lib/i18n/tasks/data/tree/siblings.rb +11 -3
- data/lib/i18n/tasks/data/tree/traversal.rb +20 -14
- data/lib/i18n/tasks/interpolations.rb +61 -3
- data/lib/i18n/tasks/logging.rb +1 -2
- data/lib/i18n/tasks/missing_keys.rb +7 -38
- data/lib/i18n/tasks/plural_keys.rb +56 -0
- data/lib/i18n/tasks/prune.rb +18 -0
- data/lib/i18n/tasks/references.rb +9 -1
- data/lib/i18n/tasks/scanners/ast_matchers/base_matcher.rb +11 -2
- data/lib/i18n/tasks/scanners/ast_matchers/rails_model_matcher.rb +16 -2
- data/lib/i18n/tasks/scanners/erb_ast_scanner.rb +6 -3
- data/lib/i18n/tasks/scanners/file_scanner.rb +4 -1
- data/lib/i18n/tasks/scanners/files/file_finder.rb +29 -6
- data/lib/i18n/tasks/scanners/files/parse_cache.rb +24 -0
- data/lib/i18n/tasks/scanners/prism_scanners/arguments_visitor.rb +33 -0
- data/lib/i18n/tasks/scanners/prism_scanners/nodes.rb +57 -23
- data/lib/i18n/tasks/scanners/prism_scanners/visitor.rb +70 -16
- data/lib/i18n/tasks/scanners/relative_keys.rb +21 -11
- data/lib/i18n/tasks/scanners/ruby_scanner.rb +37 -5
- data/lib/i18n/tasks/split_key.rb +16 -6
- data/lib/i18n/tasks/translators/base_translator.rb +25 -23
- data/lib/i18n/tasks/translators/deepl_translator.rb +18 -15
- data/lib/i18n/tasks/translators/google_translator.rb +41 -14
- data/lib/i18n/tasks/translators/openai_translator.rb +31 -17
- data/lib/i18n/tasks/translators/watsonx_translator.rb +8 -10
- data/lib/i18n/tasks/translators/yandex_translator.rb +4 -0
- data/lib/i18n/tasks/unused_keys.rb +35 -5
- data/lib/i18n/tasks/used_keys.rb +6 -1
- data/lib/i18n/tasks/version.rb +1 -1
- data/script/report_test_results.rb +58 -0
- data/templates/config/i18n-tasks.yml +11 -3
- metadata +17 -7
|
@@ -60,6 +60,36 @@ module I18n::Tasks::Translators
|
|
|
60
60
|
{format: "text"}
|
|
61
61
|
end
|
|
62
62
|
|
|
63
|
+
# Batches pairs by the request limits so a failed request only skips its own keys.
|
|
64
|
+
# A single pair can exceed the limits (e.g. a large array value), see #batch_texts.
|
|
65
|
+
def translation_batches(list, options)
|
|
66
|
+
batches = []
|
|
67
|
+
current_batch = []
|
|
68
|
+
current_text_count = 0
|
|
69
|
+
current_chars = 0
|
|
70
|
+
|
|
71
|
+
list.each do |pair|
|
|
72
|
+
pair_values = replace_newlines_with_placeholder(to_values([pair], options))
|
|
73
|
+
pair_text_count = pair_values.size
|
|
74
|
+
pair_chars = pair_values.sum(&:length)
|
|
75
|
+
|
|
76
|
+
if (current_text_count + pair_text_count > MAX_TEXTS_PER_REQUEST ||
|
|
77
|
+
current_chars + pair_chars > MAX_CHARS_PER_REQUEST) && current_batch.any?
|
|
78
|
+
batches << current_batch
|
|
79
|
+
current_batch = []
|
|
80
|
+
current_text_count = 0
|
|
81
|
+
current_chars = 0
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
current_batch << pair
|
|
85
|
+
current_text_count += pair_text_count
|
|
86
|
+
current_chars += pair_chars
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
batches << current_batch if current_batch.any?
|
|
90
|
+
batches
|
|
91
|
+
end
|
|
92
|
+
|
|
63
93
|
def no_results_error_message
|
|
64
94
|
I18n.t("i18n_tasks.google_translate.errors.no_results")
|
|
65
95
|
end
|
|
@@ -116,25 +146,23 @@ module I18n::Tasks::Translators
|
|
|
116
146
|
|
|
117
147
|
raise ArgumentError, "target language (:to) is required" if to.nil? || to.empty?
|
|
118
148
|
|
|
119
|
-
|
|
120
|
-
batches = batch_texts(texts)
|
|
121
|
-
|
|
122
|
-
batches.flat_map do |batch|
|
|
149
|
+
batch_texts(texts).flat_map do |batch|
|
|
123
150
|
translate_batch(texts: batch, api_key:, to:, from:, html:)
|
|
124
151
|
end
|
|
125
152
|
end
|
|
126
153
|
|
|
154
|
+
# Splits texts to stay within the request limits. Needed because #translation_batches
|
|
155
|
+
# cannot split a single pair, as its translated values are mapped back to it.
|
|
127
156
|
def batch_texts(texts)
|
|
128
157
|
batches = []
|
|
129
158
|
current_batch = []
|
|
130
159
|
current_chars = 0
|
|
131
160
|
|
|
132
161
|
texts.each do |text|
|
|
133
|
-
text_length = text.
|
|
162
|
+
text_length = text.length
|
|
134
163
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
(current_chars + text_length > MAX_CHARS_PER_REQUEST && current_batch.any?)
|
|
164
|
+
if (current_batch.size >= MAX_TEXTS_PER_REQUEST ||
|
|
165
|
+
current_chars + text_length > MAX_CHARS_PER_REQUEST) && current_batch.any?
|
|
138
166
|
batches << current_batch
|
|
139
167
|
current_batch = []
|
|
140
168
|
current_chars = 0
|
|
@@ -169,18 +197,17 @@ module I18n::Tasks::Translators
|
|
|
169
197
|
end
|
|
170
198
|
|
|
171
199
|
def make_request(uri, body)
|
|
172
|
-
http = Net::HTTP.new(uri.host, uri.port)
|
|
173
|
-
http.use_ssl = true
|
|
174
|
-
http.read_timeout = 60
|
|
175
|
-
http.open_timeout = 30
|
|
176
|
-
|
|
177
200
|
request = Net::HTTP::Post.new(uri.request_uri)
|
|
178
201
|
request["Content-Type"] = "application/json"
|
|
179
202
|
request["Accept"] = "application/json"
|
|
180
203
|
request["User-Agent"] = "i18n-tasks/GoogleTranslateApi"
|
|
181
204
|
request.body = JSON.generate(body)
|
|
182
205
|
|
|
183
|
-
|
|
206
|
+
# An explicit `start` block is required: on JRuby, implicit connections intermittently fail with
|
|
207
|
+
# "closing inbound before receiving peer's close_notify" when reading Google's response.
|
|
208
|
+
response = Net::HTTP.start(uri.host, uri.port, use_ssl: true, read_timeout: 60, open_timeout: 30) do |http|
|
|
209
|
+
http.request(request)
|
|
210
|
+
end
|
|
184
211
|
|
|
185
212
|
handle_error_response(response) unless response.is_a?(Net::HTTPSuccess)
|
|
186
213
|
response
|
|
@@ -50,6 +50,10 @@ module I18n::Tasks::Translators
|
|
|
50
50
|
{}
|
|
51
51
|
end
|
|
52
52
|
|
|
53
|
+
def translation_batches(list, _options)
|
|
54
|
+
list.each_slice(BATCH_SIZE).to_a
|
|
55
|
+
end
|
|
56
|
+
|
|
53
57
|
def no_results_error_message
|
|
54
58
|
I18n.t("i18n_tasks.openai_translate.errors.no_results")
|
|
55
59
|
end
|
|
@@ -77,6 +81,10 @@ module I18n::Tasks::Translators
|
|
|
77
81
|
@temperature ||= @i18n_tasks.translation_config[:openai_temperature].presence || 0.0
|
|
78
82
|
end
|
|
79
83
|
|
|
84
|
+
def reasoning_effort
|
|
85
|
+
@i18n_tasks.translation_config[:openai_reasoning_effort].presence
|
|
86
|
+
end
|
|
87
|
+
|
|
80
88
|
def system_prompt(to_locale)
|
|
81
89
|
prompt = if locale_prompts[to_locale].present?
|
|
82
90
|
locale_prompts[to_locale]
|
|
@@ -92,27 +100,33 @@ module I18n::Tasks::Translators
|
|
|
92
100
|
end
|
|
93
101
|
|
|
94
102
|
def translate_values(list, from:, to:)
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
list.each_slice(BATCH_SIZE) do |batch|
|
|
98
|
-
result = translate(batch, from, to)
|
|
99
|
-
results << result
|
|
100
|
-
|
|
101
|
-
@progress_bar.progress += result.size
|
|
102
|
-
end
|
|
103
|
+
return [] if list.empty?
|
|
103
104
|
|
|
104
|
-
|
|
105
|
+
result = translate(list, from, to)
|
|
106
|
+
@progress_bar.progress += result.size
|
|
107
|
+
result
|
|
105
108
|
end
|
|
106
109
|
|
|
107
110
|
def translate(values, from, to)
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
111
|
+
effort = reasoning_effort
|
|
112
|
+
parameters = {
|
|
113
|
+
model: model,
|
|
114
|
+
messages: build_messages(values, from, to),
|
|
115
|
+
response_format: {type: "json_object"}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
parameters[:reasoning_effort] = effort if effort
|
|
119
|
+
|
|
120
|
+
if effort && effort != "none"
|
|
121
|
+
if @i18n_tasks.translation_config[:openai_temperature].present?
|
|
122
|
+
fail ::I18n::Tasks::CommandError,
|
|
123
|
+
"openai_temperature is not supported when openai_reasoning_effort is #{effort}"
|
|
124
|
+
end
|
|
125
|
+
else
|
|
126
|
+
parameters[:temperature] = temperature
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
response = translator.chat(parameters: parameters)
|
|
116
130
|
|
|
117
131
|
translations = response.dig("choices", 0, "message", "content")
|
|
118
132
|
error = response["error"]
|
|
@@ -39,6 +39,10 @@ module I18n::Tasks::Translators
|
|
|
39
39
|
{}
|
|
40
40
|
end
|
|
41
41
|
|
|
42
|
+
def translation_batches(list, _options)
|
|
43
|
+
list.each_slice(BATCH_SIZE).to_a
|
|
44
|
+
end
|
|
45
|
+
|
|
42
46
|
def no_results_error_message
|
|
43
47
|
I18n.t("i18n_tasks.watsonx_translate.errors.no_results")
|
|
44
48
|
end
|
|
@@ -79,17 +83,11 @@ module I18n::Tasks::Translators
|
|
|
79
83
|
end
|
|
80
84
|
|
|
81
85
|
def translate_values(list, from:, to:)
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
list.each_slice(BATCH_SIZE) do |batch|
|
|
85
|
-
translations = translate(batch, from, to)
|
|
86
|
-
result = JSON.parse(translations)
|
|
87
|
-
results << result
|
|
88
|
-
|
|
89
|
-
@progress_bar.progress += results.size
|
|
90
|
-
end
|
|
86
|
+
return [] if list.empty?
|
|
91
87
|
|
|
92
|
-
|
|
88
|
+
result = JSON.parse(translate(list, from, to))
|
|
89
|
+
@progress_bar.progress += result.size
|
|
90
|
+
result
|
|
93
91
|
end
|
|
94
92
|
|
|
95
93
|
def translate(values, from, to)
|
|
@@ -7,19 +7,49 @@ module I18n
|
|
|
7
7
|
module UnusedKeys
|
|
8
8
|
def unused_keys(locales: nil, strict: nil)
|
|
9
9
|
locales = Array(locales).presence || self.locales
|
|
10
|
-
|
|
10
|
+
strict = strict?(strict)
|
|
11
|
+
return unused_forest(locales, strict: strict) if strict
|
|
12
|
+
|
|
13
|
+
# `used_in_expr?` scans the source again, non-strictly
|
|
14
|
+
parse_cache.retain { unused_forest(locales, strict: strict) }
|
|
11
15
|
end
|
|
12
16
|
|
|
13
17
|
# @param [String] locale
|
|
14
|
-
# @param [Boolean] strict if true, do not match dynamic keys
|
|
15
|
-
def unused_tree(locale: base_locale, strict: nil)
|
|
16
|
-
|
|
18
|
+
# @param [Boolean, nil] strict if true, do not match dynamic keys. Defaults to `search.strict`.
|
|
19
|
+
def unused_tree(locale: base_locale, strict: nil, used_key_names: nil)
|
|
20
|
+
strict = strict?(strict)
|
|
21
|
+
used_key_names ||= used_tree(strict: true).key_names.to_set
|
|
17
22
|
collapse_plural_nodes!(data[locale].select_keys do |key, _node|
|
|
18
23
|
!ignore_key?(key, :unused) &&
|
|
19
24
|
(strict || !used_in_expr?(key)) &&
|
|
20
|
-
!
|
|
25
|
+
!key_used?(depluralize_key(key, locale), used_key_names)
|
|
21
26
|
end)
|
|
22
27
|
end
|
|
28
|
+
|
|
29
|
+
private
|
|
30
|
+
|
|
31
|
+
def unused_forest(locales, strict:)
|
|
32
|
+
used_key_names = used_tree(strict: true).key_names.to_set
|
|
33
|
+
locales.map { |locale| unused_tree(locale: locale, strict: strict, used_key_names: used_key_names) }.compact.reduce(:merge!)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# A key is considered used if it is directly used, or if one of its ancestors is used
|
|
37
|
+
# (e.g. `t(:section)` covers `section.item.title`).
|
|
38
|
+
def key_used?(key, used_key_names)
|
|
39
|
+
return true if used_key_names.include?(key)
|
|
40
|
+
|
|
41
|
+
ancestor = key
|
|
42
|
+
loop do
|
|
43
|
+
next_ancestor = ancestor.sub(/\.[^.]+\z/, "")
|
|
44
|
+
break if next_ancestor == ancestor
|
|
45
|
+
|
|
46
|
+
return true if used_key_names.include?(next_ancestor)
|
|
47
|
+
|
|
48
|
+
ancestor = next_ancestor
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
false
|
|
52
|
+
end
|
|
23
53
|
end
|
|
24
54
|
end
|
|
25
55
|
end
|
data/lib/i18n/tasks/used_keys.rb
CHANGED
|
@@ -81,7 +81,8 @@ module I18n::Tasks
|
|
|
81
81
|
ActiveSupport::Inflector.constantize(class_name).new(
|
|
82
82
|
config: merge_scanner_configs(shared_options, args || {}),
|
|
83
83
|
file_finder_provider: caching_file_finder_provider,
|
|
84
|
-
file_reader: caching_file_reader
|
|
84
|
+
file_reader: caching_file_reader,
|
|
85
|
+
parse_cache: parse_cache
|
|
85
86
|
)
|
|
86
87
|
end.tap { |scanners| log_verbose { scanners.map { |s| " #{s.class.name} #{s.config.inspect}" } * "\n" } }
|
|
87
88
|
)
|
|
@@ -123,6 +124,10 @@ module I18n::Tasks
|
|
|
123
124
|
@caching_file_finder_provider ||= Scanners::Files::CachingFileFinderProvider.new(exclude: ALWAYS_EXCLUDE)
|
|
124
125
|
end
|
|
125
126
|
|
|
127
|
+
def parse_cache
|
|
128
|
+
@parse_cache ||= Scanners::Files::ParseCache.new
|
|
129
|
+
end
|
|
130
|
+
|
|
126
131
|
def caching_file_reader
|
|
127
132
|
@caching_file_reader ||= Scanners::Files::CachingFileReader.new
|
|
128
133
|
end
|
data/lib/i18n/tasks/version.rb
CHANGED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "rexml/document"
|
|
4
|
+
|
|
5
|
+
def escape_property(value)
|
|
6
|
+
value.gsub("%", "%25").gsub("\r", "%0D").gsub("\n", "%0A").gsub(":", "%3A").gsub(",", "%2C")
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def escape_data(value)
|
|
10
|
+
value.gsub("%", "%25").gsub("\r", "%0D").gsub("\n", "%0A")
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
reports = Dir.glob(File.join(ARGV.fetch(0, "test-results"), "**", "*.xml")).sort
|
|
14
|
+
summary = ["## RSpec results", ""]
|
|
15
|
+
|
|
16
|
+
if reports.empty?
|
|
17
|
+
summary << "No JUnit reports were uploaded."
|
|
18
|
+
puts "::warning::No JUnit reports were uploaded."
|
|
19
|
+
else
|
|
20
|
+
summary << "| Ruby | Tests | Failures | Errors | Skipped |"
|
|
21
|
+
summary << "| --- | ---: | ---: | ---: | ---: |"
|
|
22
|
+
|
|
23
|
+
reports.each do |report|
|
|
24
|
+
ruby_version = File.basename(File.dirname(report)).delete_prefix("test-results-")
|
|
25
|
+
document = REXML::Document.new(File.read(report))
|
|
26
|
+
cases = []
|
|
27
|
+
document.elements.each("//testcase") { |test_case| cases << test_case }
|
|
28
|
+
|
|
29
|
+
failed = cases.count { |test_case| test_case.elements["failure"] }
|
|
30
|
+
errors = cases.count { |test_case| test_case.elements["error"] }
|
|
31
|
+
skipped = cases.count { |test_case| test_case.elements["skipped"] }
|
|
32
|
+
summary << "| #{ruby_version} | #{cases.size} | #{failed} | #{errors} | #{skipped} |"
|
|
33
|
+
|
|
34
|
+
cases.each do |test_case|
|
|
35
|
+
%w[failure error].each do |result|
|
|
36
|
+
issue = test_case.elements[result]
|
|
37
|
+
next unless issue
|
|
38
|
+
|
|
39
|
+
name = [test_case.attributes["classname"], test_case.attributes["name"]].compact.join(" ")
|
|
40
|
+
message = [issue.attributes["message"], issue.text].compact.reject(&:empty?).join(": ")
|
|
41
|
+
location = test_case.attributes["file"]
|
|
42
|
+
details = "#{ruby_version}: #{name} - #{message}"
|
|
43
|
+
summary << "- **#{ruby_version}** `#{name}`: #{message.to_s.gsub("\n", " ")}"
|
|
44
|
+
|
|
45
|
+
properties = ["title=RSpec #{result}"]
|
|
46
|
+
properties << "file=#{location}" if location
|
|
47
|
+
escaped_properties = properties.map { |property| escape_property(property) }.join(",")
|
|
48
|
+
puts "::error #{escaped_properties}::#{escape_data(details)}"
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
if (summary_path = ENV["GITHUB_STEP_SUMMARY"])
|
|
55
|
+
File.open(summary_path, "a") { |file| file.puts(summary.join("\n")) }
|
|
56
|
+
else
|
|
57
|
+
puts summary.join("\n")
|
|
58
|
+
end
|
|
@@ -13,7 +13,9 @@ data:
|
|
|
13
13
|
## Provide a custom adapter:
|
|
14
14
|
# adapter: I18n::Tasks::Data::FileSystem
|
|
15
15
|
|
|
16
|
-
# Locale files or `Dir.glob` patterns where translations are read from
|
|
16
|
+
# Locale files or `Dir.glob` patterns where translations are read from.
|
|
17
|
+
# When a key is defined in several files, the file read last wins: patterns are read in order,
|
|
18
|
+
# and the files matching a pattern in the order `Dir.glob` returns them.
|
|
17
19
|
read:
|
|
18
20
|
## Default:
|
|
19
21
|
# - config/locales/%{locale}.yml
|
|
@@ -115,8 +117,7 @@ search:
|
|
|
115
117
|
# # deepl_host: "https://api.deepl.com"
|
|
116
118
|
# # deepl_version: "v2"
|
|
117
119
|
# # deepl_glossary_ids:
|
|
118
|
-
# # -
|
|
119
|
-
# # - 2c6415be-1852-4f54-9e1b-d800463496b4
|
|
120
|
+
# # - uuid
|
|
120
121
|
# # add additional options to the DeepL.translate call: https://www.deepl.com/docs-api/translate-text/translate-text/
|
|
121
122
|
# deepl_options:
|
|
122
123
|
# formality: prefer_less
|
|
@@ -153,6 +154,13 @@ search:
|
|
|
153
154
|
# - 'errors.messages.{accepted,blank,invalid,too_short,too_long}'
|
|
154
155
|
# - '{devise,simple_form}.*'
|
|
155
156
|
|
|
157
|
+
## Alternatively, ignore missing keys per locale (just like ignore_eq_base):
|
|
158
|
+
# ignore_missing:
|
|
159
|
+
# all:
|
|
160
|
+
# - 'errors.messages.{accepted,blank,invalid,too_short,too_long}'
|
|
161
|
+
# fr,es:
|
|
162
|
+
# - '{devise,simple_form}.*'
|
|
163
|
+
|
|
156
164
|
## Consider these keys used:
|
|
157
165
|
# ignore_unused:
|
|
158
166
|
# - 'activerecord.attributes.*'
|
metadata
CHANGED
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: i18n-tasks
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.
|
|
4
|
+
version: 1.2.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- glebm
|
|
8
|
-
autorequire:
|
|
9
8
|
bindir: bin
|
|
10
9
|
cert_chain: []
|
|
11
|
-
date:
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
12
11
|
dependencies:
|
|
13
12
|
- !ruby/object:Gem::Dependency
|
|
14
13
|
name: activesupport
|
|
@@ -100,14 +99,14 @@ dependencies:
|
|
|
100
99
|
requirements:
|
|
101
100
|
- - ">="
|
|
102
101
|
- !ruby/object:Gem::Version
|
|
103
|
-
version: '0'
|
|
102
|
+
version: '0.28'
|
|
104
103
|
type: :runtime
|
|
105
104
|
prerelease: false
|
|
106
105
|
version_requirements: !ruby/object:Gem::Requirement
|
|
107
106
|
requirements:
|
|
108
107
|
- - ">="
|
|
109
108
|
- !ruby/object:Gem::Version
|
|
110
|
-
version: '0'
|
|
109
|
+
version: '0.28'
|
|
111
110
|
- !ruby/object:Gem::Dependency
|
|
112
111
|
name: rails-i18n
|
|
113
112
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -191,6 +190,14 @@ files:
|
|
|
191
190
|
- LICENSE.txt
|
|
192
191
|
- README.md
|
|
193
192
|
- Rakefile
|
|
193
|
+
- benchmarks/bench_helper.rb
|
|
194
|
+
- benchmarks/data_bench.rb
|
|
195
|
+
- benchmarks/end_to_end_bench.rb
|
|
196
|
+
- benchmarks/fixtures/generator.rb
|
|
197
|
+
- benchmarks/results/baseline.json
|
|
198
|
+
- benchmarks/run_all.rb
|
|
199
|
+
- benchmarks/scanning_bench.rb
|
|
200
|
+
- benchmarks/tree_bench.rb
|
|
194
201
|
- bin/i18n-tasks
|
|
195
202
|
- bin/i18n-tasks.cmd
|
|
196
203
|
- config/locales/en.yml
|
|
@@ -208,6 +215,7 @@ files:
|
|
|
208
215
|
- lib/i18n/tasks/command/commands/interpolations.rb
|
|
209
216
|
- lib/i18n/tasks/command/commands/meta.rb
|
|
210
217
|
- lib/i18n/tasks/command/commands/missing.rb
|
|
218
|
+
- lib/i18n/tasks/command/commands/prune.rb
|
|
211
219
|
- lib/i18n/tasks/command/commands/tree.rb
|
|
212
220
|
- lib/i18n/tasks/command/commands/usages.rb
|
|
213
221
|
- lib/i18n/tasks/command/dsl.rb
|
|
@@ -245,6 +253,7 @@ files:
|
|
|
245
253
|
- lib/i18n/tasks/logging.rb
|
|
246
254
|
- lib/i18n/tasks/missing_keys.rb
|
|
247
255
|
- lib/i18n/tasks/plural_keys.rb
|
|
256
|
+
- lib/i18n/tasks/prune.rb
|
|
248
257
|
- lib/i18n/tasks/references.rb
|
|
249
258
|
- lib/i18n/tasks/reports/base.rb
|
|
250
259
|
- lib/i18n/tasks/reports/terminal.rb
|
|
@@ -259,6 +268,7 @@ files:
|
|
|
259
268
|
- lib/i18n/tasks/scanners/files/caching_file_reader.rb
|
|
260
269
|
- lib/i18n/tasks/scanners/files/file_finder.rb
|
|
261
270
|
- lib/i18n/tasks/scanners/files/file_reader.rb
|
|
271
|
+
- lib/i18n/tasks/scanners/files/parse_cache.rb
|
|
262
272
|
- lib/i18n/tasks/scanners/local_ruby_parser.rb
|
|
263
273
|
- lib/i18n/tasks/scanners/occurrence_from_position.rb
|
|
264
274
|
- lib/i18n/tasks/scanners/pattern_mapper.rb
|
|
@@ -289,6 +299,7 @@ files:
|
|
|
289
299
|
- lib/i18n/tasks/unused_keys.rb
|
|
290
300
|
- lib/i18n/tasks/used_keys.rb
|
|
291
301
|
- lib/i18n/tasks/version.rb
|
|
302
|
+
- script/report_test_results.rb
|
|
292
303
|
- templates/config/i18n-tasks.yml
|
|
293
304
|
- templates/minitest/i18n_test.rb
|
|
294
305
|
- templates/rspec/i18n_spec.rb
|
|
@@ -321,8 +332,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
321
332
|
- !ruby/object:Gem::Version
|
|
322
333
|
version: '0'
|
|
323
334
|
requirements: []
|
|
324
|
-
rubygems_version:
|
|
325
|
-
signing_key:
|
|
335
|
+
rubygems_version: 4.0.22
|
|
326
336
|
specification_version: 4
|
|
327
337
|
summary: Manage localization and translation with the awesome power of static analysis
|
|
328
338
|
test_files: []
|