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,6 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require
|
|
3
|
+
require "i18n/tasks/data/tree/traversal"
|
|
4
4
|
module I18n::Tasks::Data::Tree
|
|
5
5
|
# A list of nodes
|
|
6
6
|
class Nodes
|
|
@@ -20,7 +20,7 @@ module I18n::Tasks::Data::Tree
|
|
|
20
20
|
end
|
|
21
21
|
|
|
22
22
|
def attributes
|
|
23
|
-
{
|
|
23
|
+
{nodes: @list}
|
|
24
24
|
end
|
|
25
25
|
|
|
26
26
|
def derive(new_attr = {})
|
|
@@ -32,10 +32,10 @@ module I18n::Tasks::Data::Tree
|
|
|
32
32
|
|
|
33
33
|
def to_hash(sort = false)
|
|
34
34
|
(@hash ||= {})[sort] ||= if sort
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
35
|
+
sort_by(&:key)
|
|
36
|
+
else
|
|
37
|
+
self
|
|
38
|
+
end.map { |node| node.to_hash(sort) }.reduce({}, :deep_merge!)
|
|
39
39
|
end
|
|
40
40
|
|
|
41
41
|
delegate :to_json, to: :to_hash
|
|
@@ -45,7 +45,7 @@ module I18n::Tasks::Data::Tree
|
|
|
45
45
|
if present?
|
|
46
46
|
map(&:inspect) * "\n"
|
|
47
47
|
else
|
|
48
|
-
Rainbow(
|
|
48
|
+
Rainbow("{∅}").faint
|
|
49
49
|
end
|
|
50
50
|
end
|
|
51
51
|
|
|
@@ -67,14 +67,14 @@ module I18n::Tasks::Data::Tree
|
|
|
67
67
|
derive.append!(other)
|
|
68
68
|
end
|
|
69
69
|
|
|
70
|
-
|
|
70
|
+
alias_method :<<, :append
|
|
71
71
|
|
|
72
72
|
def merge!(nodes)
|
|
73
73
|
@list += nodes.to_a
|
|
74
74
|
dirty!
|
|
75
75
|
self
|
|
76
76
|
end
|
|
77
|
-
|
|
77
|
+
alias_method :+, :merge!
|
|
78
78
|
|
|
79
79
|
def children(&block)
|
|
80
80
|
return to_enum(:children) { map { |c| c.children ? c.children.size : 0 }.reduce(:+) } unless block
|
|
@@ -84,7 +84,7 @@ module I18n::Tasks::Data::Tree
|
|
|
84
84
|
end
|
|
85
85
|
end
|
|
86
86
|
|
|
87
|
-
|
|
87
|
+
alias_method :children?, :any?
|
|
88
88
|
|
|
89
89
|
protected
|
|
90
90
|
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require
|
|
4
|
-
require
|
|
5
|
-
require
|
|
3
|
+
require "set"
|
|
4
|
+
require "i18n/tasks/split_key"
|
|
5
|
+
require "i18n/tasks/data/tree/nodes"
|
|
6
6
|
|
|
7
7
|
module I18n::Tasks::Data::Tree
|
|
8
8
|
# Siblings represents a subtree sharing a common parent
|
|
@@ -17,7 +17,7 @@ module I18n::Tasks::Data::Tree
|
|
|
17
17
|
def initialize(opts = {})
|
|
18
18
|
super(nodes: opts[:nodes])
|
|
19
19
|
@parent = opts[:parent] || first.try(:parent)
|
|
20
|
-
@list.map! { |node| node.parent == @parent ? node : node.derive(parent: @parent) }
|
|
20
|
+
@list.map! { |node| (node.parent == @parent) ? node : node.derive(parent: @parent) }
|
|
21
21
|
@key_to_node = @list.each_with_object({}) { |node, h| h[node.key] = node }
|
|
22
22
|
@warn_about_add_children_to_leaf = opts.fetch(:warn_about_add_children_to_leaf, true)
|
|
23
23
|
end
|
|
@@ -52,7 +52,7 @@ module I18n::Tasks::Data::Tree
|
|
|
52
52
|
new_key = to_pattern.gsub(/\\\d+/) { |m| match[m[1..].to_i] }
|
|
53
53
|
old_key_to_new_key[full_key] = new_key
|
|
54
54
|
moved_forest.merge!(Siblings.new.tap do |forest|
|
|
55
|
-
forest[[(node.root.try(:key) unless root), new_key].compact.join(
|
|
55
|
+
forest[[(node.root.try(:key) unless root), new_key].compact.join(".")] =
|
|
56
56
|
node.derive(key: split_key(new_key).last)
|
|
57
57
|
end)
|
|
58
58
|
end
|
|
@@ -62,10 +62,10 @@ module I18n::Tasks::Data::Tree
|
|
|
62
62
|
nodes do |node|
|
|
63
63
|
next unless node.reference?
|
|
64
64
|
|
|
65
|
-
old_target = [(node.root.key if root), node.value.to_s].compact.join(
|
|
65
|
+
old_target = [(node.root.key if root), node.value.to_s].compact.join(".")
|
|
66
66
|
new_target = old_key_to_new_key[old_target]
|
|
67
67
|
if new_target
|
|
68
|
-
new_target = new_target.sub(/\A[^.]*\./,
|
|
68
|
+
new_target = new_target.sub(/\A[^.]*\./, "") if root
|
|
69
69
|
node.value = new_target.to_sym
|
|
70
70
|
end
|
|
71
71
|
end
|
|
@@ -75,26 +75,26 @@ module I18n::Tasks::Data::Tree
|
|
|
75
75
|
end
|
|
76
76
|
|
|
77
77
|
def replace_node!(node, new_node)
|
|
78
|
-
@list[@list.index(node)]
|
|
78
|
+
@list[@list.index(node)] = new_node
|
|
79
79
|
key_to_node[new_node.key] = new_node
|
|
80
80
|
end
|
|
81
81
|
|
|
82
82
|
# @return [Node] by full key
|
|
83
83
|
def get(full_key)
|
|
84
84
|
first_key, rest = split_key(full_key.to_s, 2)
|
|
85
|
-
node
|
|
85
|
+
node = key_to_node[first_key]
|
|
86
86
|
node = node.children.try(:get, rest) if rest && node
|
|
87
87
|
node
|
|
88
88
|
end
|
|
89
89
|
|
|
90
|
-
|
|
90
|
+
alias_method :[], :get
|
|
91
91
|
|
|
92
92
|
# add or replace node by full key
|
|
93
93
|
def set(full_key, node)
|
|
94
|
-
fail
|
|
94
|
+
fail "value should be a I18n::Tasks::Data::Tree::Node" unless node.is_a?(Node)
|
|
95
95
|
|
|
96
96
|
key_part, rest = split_key(full_key, 2)
|
|
97
|
-
child
|
|
97
|
+
child = key_to_node[key_part]
|
|
98
98
|
|
|
99
99
|
if rest
|
|
100
100
|
unless child
|
|
@@ -119,7 +119,7 @@ module I18n::Tasks::Data::Tree
|
|
|
119
119
|
node
|
|
120
120
|
end
|
|
121
121
|
|
|
122
|
-
|
|
122
|
+
alias_method :[]=, :set
|
|
123
123
|
|
|
124
124
|
# methods below change state
|
|
125
125
|
|
|
@@ -133,9 +133,9 @@ module I18n::Tasks::Data::Tree
|
|
|
133
133
|
nodes = nodes.map do |node|
|
|
134
134
|
fail "already has a child with key '#{node.key}'" if key_to_node.key?(node.key)
|
|
135
135
|
|
|
136
|
-
key_to_node[node.key] = (node.parent == parent ? node : node.derive(parent: parent))
|
|
136
|
+
key_to_node[node.key] = ((node.parent == parent) ? node : node.derive(parent: parent))
|
|
137
137
|
end
|
|
138
|
-
super
|
|
138
|
+
super
|
|
139
139
|
self
|
|
140
140
|
end
|
|
141
141
|
|
|
@@ -267,7 +267,7 @@ module I18n::Tasks::Data::Tree
|
|
|
267
267
|
key_occurrences.each do |key_occurrence|
|
|
268
268
|
forest[key_occurrence.key] = ::I18n::Tasks::Data::Tree::Node.new(
|
|
269
269
|
key: split_key(key_occurrence.key).last,
|
|
270
|
-
data: {
|
|
270
|
+
data: {occurrences: key_occurrence.occurrences}
|
|
271
271
|
)
|
|
272
272
|
end
|
|
273
273
|
end
|
|
@@ -276,9 +276,9 @@ module I18n::Tasks::Data::Tree
|
|
|
276
276
|
def from_key_attr(key_attrs, opts = {}, &block)
|
|
277
277
|
build_forest(opts) do |forest|
|
|
278
278
|
key_attrs.each do |(full_key, attr)|
|
|
279
|
-
fail "Invalid key #{full_key.inspect}" if full_key.end_with?(
|
|
279
|
+
fail "Invalid key #{full_key.inspect}" if full_key.end_with?(".")
|
|
280
280
|
|
|
281
|
-
node = ::I18n::Tasks::Data::Tree::Node.new(**attr
|
|
281
|
+
node = ::I18n::Tasks::Data::Tree::Node.new(**attr, key: split_key(full_key).last)
|
|
282
282
|
yield(full_key, node) if block
|
|
283
283
|
forest[full_key] = node
|
|
284
284
|
end
|
|
@@ -305,7 +305,7 @@ module I18n::Tasks::Data::Tree
|
|
|
305
305
|
Siblings.new(opts)
|
|
306
306
|
end
|
|
307
307
|
|
|
308
|
-
|
|
308
|
+
alias_method :[], :from_nested_hash
|
|
309
309
|
|
|
310
310
|
# build forest from [[Full Key, Value]]
|
|
311
311
|
def from_flat_pairs(pairs)
|
|
@@ -323,7 +323,7 @@ module I18n::Tasks::Data::Tree
|
|
|
323
323
|
opts[:parent] = ::I18n::Tasks::Data::Tree::Node.new(opts[:parent_attr]) if opts[:parent_attr]
|
|
324
324
|
if opts[:parent_locale]
|
|
325
325
|
opts[:parent] = ::I18n::Tasks::Data::Tree::Node.new(
|
|
326
|
-
key: opts[:parent_locale], data: {
|
|
326
|
+
key: opts[:parent_locale], data: {locale: opts[:parent_locale]}
|
|
327
327
|
)
|
|
328
328
|
end
|
|
329
329
|
end
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require
|
|
3
|
+
require "set"
|
|
4
4
|
|
|
5
5
|
module I18n::Tasks
|
|
6
6
|
module Data::Tree
|
|
7
7
|
# Any Enumerable that yields nodes can mix in this module
|
|
8
8
|
module Traversal # rubocop:disable Metrics/ModuleLength
|
|
9
|
-
def nodes(&
|
|
10
|
-
depth_first(&
|
|
9
|
+
def nodes(&)
|
|
10
|
+
depth_first(&)
|
|
11
11
|
end
|
|
12
12
|
|
|
13
13
|
def leaves(&visitor)
|
|
@@ -76,13 +76,13 @@ module I18n::Tasks
|
|
|
76
76
|
|
|
77
77
|
def root_key_values(sort = false)
|
|
78
78
|
result = keys(root: false).map { |key, node| [node.root.key, key, node.value] }
|
|
79
|
-
result.sort! { |a, b| a[0] == b[0] ? a[1] <=> b[1] : a[0] <=> b[0] } if sort
|
|
79
|
+
result.sort! { |a, b| (a[0] == b[0]) ? a[1] <=> b[1] : a[0] <=> b[0] } if sort
|
|
80
80
|
result
|
|
81
81
|
end
|
|
82
82
|
|
|
83
83
|
def root_key_value_data(sort = false)
|
|
84
84
|
result = keys(root: false).map { |key, node| [node.root.key, key, node.value, node.data] }
|
|
85
|
-
result.sort! { |a, b| a[0] == b[0] ? a[1] <=> b[1] : a[0] <=> b[0] } if sort
|
|
85
|
+
result.sort! { |a, b| (a[0] == b[0]) ? a[1] <=> b[1] : a[0] <=> b[0] } if sort
|
|
86
86
|
result
|
|
87
87
|
end
|
|
88
88
|
|
data/lib/i18n/tasks/data.rb
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require
|
|
3
|
+
require "i18n/tasks/data/file_system"
|
|
4
4
|
|
|
5
5
|
module I18n::Tasks
|
|
6
6
|
module Data
|
|
7
7
|
DATA_DEFAULTS = {
|
|
8
|
-
adapter:
|
|
8
|
+
adapter: "I18n::Tasks::Data::FileSystem"
|
|
9
9
|
}.freeze
|
|
10
10
|
|
|
11
11
|
# I18n data provider
|
|
@@ -17,7 +17,7 @@ module I18n::Tasks
|
|
|
17
17
|
data_config[:locales] = config[:locales]
|
|
18
18
|
adapter_class = data_config[:adapter].presence || data_config[:class].presence || DATA_DEFAULTS[:adapter]
|
|
19
19
|
adapter_class = adapter_class.to_s
|
|
20
|
-
adapter_class =
|
|
20
|
+
adapter_class = "I18n::Tasks::Data::FileSystem" if adapter_class == "file_system"
|
|
21
21
|
data_config.except!(:adapter, :class)
|
|
22
22
|
ActiveSupport::Inflector.constantize(adapter_class).new data_config
|
|
23
23
|
end
|
|
@@ -50,7 +50,7 @@ module I18n::Tasks
|
|
|
50
50
|
end
|
|
51
51
|
|
|
52
52
|
def t_proc(locale = base_locale)
|
|
53
|
-
@t_proc
|
|
53
|
+
@t_proc ||= {}
|
|
54
54
|
@t_proc[locale] ||= proc { |key| t(key, locale) }
|
|
55
55
|
end
|
|
56
56
|
|
data/lib/i18n/tasks/html_keys.rb
CHANGED
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
module I18n::Tasks
|
|
4
4
|
module HtmlKeys
|
|
5
|
-
HTML_KEY_PATTERN = /[.\-_]html\z
|
|
6
|
-
MAYBE_PLURAL_HTML_KEY_PATTERN = /[.\-_]html\.[^.]+\z
|
|
5
|
+
HTML_KEY_PATTERN = /[.\-_]html\z/
|
|
6
|
+
MAYBE_PLURAL_HTML_KEY_PATTERN = /[.\-_]html\.[^.]+\z/
|
|
7
7
|
|
|
8
8
|
def html_key?(full_key, locale)
|
|
9
9
|
!!(full_key =~ HTML_KEY_PATTERN ||
|
|
@@ -12,19 +12,19 @@ module I18n::Tasks::IgnoreKeys
|
|
|
12
12
|
# @param locale [String] only when type is :eq_base
|
|
13
13
|
# @return [Regexp] a regexp that matches all the keys ignored for the type (and locale)
|
|
14
14
|
def ignore_pattern(type, locale = nil)
|
|
15
|
-
@ignore_patterns
|
|
16
|
-
@ignore_patterns[type]
|
|
15
|
+
@ignore_patterns ||= HashWithIndifferentAccess.new
|
|
16
|
+
@ignore_patterns[type] ||= {}
|
|
17
17
|
@ignore_patterns[type][locale] ||= begin
|
|
18
18
|
global = ignore_config.presence || []
|
|
19
19
|
type_ignore = ignore_config(type).presence || []
|
|
20
20
|
patterns = case type_ignore
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
21
|
+
when Array
|
|
22
|
+
global + type_ignore
|
|
23
|
+
when Hash
|
|
24
|
+
# ignore per locale
|
|
25
|
+
global + (type_ignore["all"] || []) +
|
|
26
|
+
type_ignore.select { |k, _v| k.to_s =~ /\b#{locale}\b/ }.values.flatten(1).compact
|
|
27
|
+
end
|
|
28
28
|
compile_patterns_re patterns
|
|
29
29
|
end
|
|
30
30
|
end
|
|
@@ -5,7 +5,7 @@ module I18n::Tasks
|
|
|
5
5
|
class << self
|
|
6
6
|
attr_accessor :variable_regex
|
|
7
7
|
end
|
|
8
|
-
@variable_regex = /(?<!%)%{[^}]+}
|
|
8
|
+
@variable_regex = /(?<!%)%{[^}]+}/
|
|
9
9
|
|
|
10
10
|
def inconsistent_interpolations(locales: nil, base_locale: nil) # rubocop:disable Metrics/AbcSize
|
|
11
11
|
locales ||= self.locales
|
|
@@ -30,5 +30,25 @@ module I18n::Tasks
|
|
|
30
30
|
result.each { |root| root.data[:type] = :inconsistent_interpolations }
|
|
31
31
|
result
|
|
32
32
|
end
|
|
33
|
+
|
|
34
|
+
def reserved_interpolations(locales: nil)
|
|
35
|
+
locales ||= self.locales
|
|
36
|
+
result = empty_forest
|
|
37
|
+
|
|
38
|
+
locales.each do |current_locale|
|
|
39
|
+
data[current_locale].key_values.each do |key, value|
|
|
40
|
+
next unless value.is_a?(String)
|
|
41
|
+
|
|
42
|
+
reserved_variables = value.scan(::I18n.reserved_keys_pattern).flatten
|
|
43
|
+
next if reserved_variables.empty?
|
|
44
|
+
|
|
45
|
+
result.merge!(data[current_locale].first.children[key].walk_to_root.reduce(nil) do |c, p|
|
|
46
|
+
[p.derive(children: c, value: reserved_variables)]
|
|
47
|
+
end)
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
result.each { |root| root.data[:type] = :inconsistent_interpolations }
|
|
51
|
+
result
|
|
52
|
+
end
|
|
33
53
|
end
|
|
34
54
|
end
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require
|
|
3
|
+
require "strscan"
|
|
4
4
|
|
|
5
5
|
module I18n::Tasks::KeyPatternMatching
|
|
6
6
|
extend self # rubocop:disable Style/ModuleFunction
|
|
7
7
|
|
|
8
|
-
MATCH_NOTHING = /\z\A
|
|
8
|
+
MATCH_NOTHING = /\z\A/
|
|
9
9
|
|
|
10
10
|
# one regex to match any
|
|
11
11
|
def compile_patterns_re(key_patterns)
|
|
@@ -13,7 +13,7 @@ module I18n::Tasks::KeyPatternMatching
|
|
|
13
13
|
# match nothing
|
|
14
14
|
MATCH_NOTHING
|
|
15
15
|
else
|
|
16
|
-
/(?:#{key_patterns.map { |p| compile_key_pattern p } *
|
|
16
|
+
/(?:#{key_patterns.map { |p| compile_key_pattern p } * "|"})/m
|
|
17
17
|
end
|
|
18
18
|
end
|
|
19
19
|
|
|
@@ -31,10 +31,10 @@ module I18n::Tasks::KeyPatternMatching
|
|
|
31
31
|
|
|
32
32
|
def key_pattern_re_body(key_pattern)
|
|
33
33
|
key_pattern
|
|
34
|
-
.gsub(
|
|
35
|
-
.gsub(
|
|
36
|
-
.gsub(
|
|
37
|
-
.gsub(
|
|
38
|
-
.gsub(/\{(.*?)}/) { "(#{Regexp.last_match(1).strip.gsub(/\s*,\s*/,
|
|
34
|
+
.gsub(".", '\.')
|
|
35
|
+
.gsub("*:", "[^.]+?")
|
|
36
|
+
.gsub("*", ".*")
|
|
37
|
+
.gsub(":", '(?<=^|\.)[^.]+?(?=\.|$)')
|
|
38
|
+
.gsub(/\{(.*?)}/) { "(#{Regexp.last_match(1).strip.gsub(/\s*,\s*/, "|")})" }
|
|
39
39
|
end
|
|
40
40
|
end
|
data/lib/i18n/tasks/logging.rb
CHANGED
|
@@ -25,7 +25,8 @@ module I18n::Tasks::Logging
|
|
|
25
25
|
def log_stderr(*args)
|
|
26
26
|
# We don't want output from different threads to get intermixed.
|
|
27
27
|
MUTEX.synchronize do
|
|
28
|
-
$stderr
|
|
28
|
+
# Use $stderr directly to avoid issues with JRuby and thread safety
|
|
29
|
+
$stderr.puts(*args) # rubocop:disable Style/StderrPuts
|
|
29
30
|
end
|
|
30
31
|
end
|
|
31
32
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require
|
|
3
|
+
require "set"
|
|
4
4
|
module I18n::Tasks
|
|
5
5
|
module MissingKeys # rubocop:disable Metrics/ModuleLength
|
|
6
6
|
MISSING_TYPES = %w[
|
|
@@ -21,7 +21,7 @@ module I18n::Tasks
|
|
|
21
21
|
# @return [Siblings]
|
|
22
22
|
def missing_keys(locales: nil, types: nil, base_locale: nil)
|
|
23
23
|
locales ||= self.locales
|
|
24
|
-
types
|
|
24
|
+
types ||= missing_keys_types
|
|
25
25
|
base = base_locale || self.base_locale
|
|
26
26
|
types.inject(empty_forest) do |f, type|
|
|
27
27
|
f.merge! send(:"missing_#{type}_forest", locales, base)
|
|
@@ -88,7 +88,7 @@ module I18n::Tasks
|
|
|
88
88
|
|
|
89
89
|
# Loads rails-i18n pluralization config for the given locale.
|
|
90
90
|
def load_rails_i18n_pluralization!(locale)
|
|
91
|
-
path = File.join(Gem::Specification.find_by_name(
|
|
91
|
+
path = File.join(Gem::Specification.find_by_name("rails-i18n").gem_dir, "rails", "pluralization", "#{locale}.rb")
|
|
92
92
|
eval(File.read(path), binding, path) # rubocop:disable Security/Eval
|
|
93
93
|
end
|
|
94
94
|
|
|
@@ -98,7 +98,7 @@ module I18n::Tasks
|
|
|
98
98
|
locale_key_missing? locale, depluralize_key(key, compared_to)
|
|
99
99
|
end.set_root_key!(locale, type: :missing_diff).keys do |_key, node|
|
|
100
100
|
# change path and locale to base
|
|
101
|
-
data = {
|
|
101
|
+
data = {locale: locale, missing_diff_locale: node.data[:locale]}
|
|
102
102
|
if node.data.key?(:path)
|
|
103
103
|
data[:path] = LocalePathname.replace_locale(node.data[:path], node.data[:locale], locale)
|
|
104
104
|
end
|
|
@@ -108,8 +108,24 @@ module I18n::Tasks
|
|
|
108
108
|
|
|
109
109
|
# keys used in the code missing translations in locale
|
|
110
110
|
def missing_used_tree(locale)
|
|
111
|
-
used_tree(strict: true).select_keys do |key,
|
|
112
|
-
|
|
111
|
+
used_tree(strict: true).select_keys do |key, node|
|
|
112
|
+
occurrences = node.data[:occurrences] || []
|
|
113
|
+
|
|
114
|
+
# An occurrence may carry candidate keys (for relative lookups). If any
|
|
115
|
+
# candidate key exists in the locale, the usage is considered present.
|
|
116
|
+
occurrences_all_missing = occurrences.all? do |occ|
|
|
117
|
+
candidates = if occ.respond_to?(:candidate_keys) && occ.candidate_keys.present?
|
|
118
|
+
occ.candidate_keys
|
|
119
|
+
else
|
|
120
|
+
# fallback to the scanned key
|
|
121
|
+
[key]
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
# Occurrence is missing iff all its candidates are missing
|
|
125
|
+
candidates.all? { |c| locale_key_missing?(locale, c) }
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
occurrences_all_missing
|
|
113
129
|
end.set_root_key!(locale, type: :missing_used)
|
|
114
130
|
end
|
|
115
131
|
|
|
@@ -130,7 +146,7 @@ module I18n::Tasks
|
|
|
130
146
|
# @yieldreturn [Boolean] whether to collapse the node
|
|
131
147
|
def collapse_same_key_in_locales!(forest)
|
|
132
148
|
locales_and_node_by_key = {}
|
|
133
|
-
to_remove
|
|
149
|
+
to_remove = []
|
|
134
150
|
forest.each do |root|
|
|
135
151
|
locale = root.key
|
|
136
152
|
root.keys do |key, node|
|
|
@@ -146,7 +162,7 @@ module I18n::Tasks
|
|
|
146
162
|
end
|
|
147
163
|
forest.remove_nodes_and_emptied_ancestors! to_remove
|
|
148
164
|
locales_and_node_by_key.each_with_object({}) do |(key, (locales, node)), inv|
|
|
149
|
-
(inv[locales.sort.join(
|
|
165
|
+
(inv[locales.sort.join("+")] ||= []) << [key, node]
|
|
150
166
|
end.map do |locales, keys_nodes|
|
|
151
167
|
keys_nodes.each do |(key, node)|
|
|
152
168
|
forest["#{locales}.#{key}"] = node
|
|
@@ -1,18 +1,20 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require
|
|
3
|
+
require "set"
|
|
4
4
|
module I18n::Tasks::PluralKeys
|
|
5
5
|
# Ref: http://cldr.unicode.org/index/cldr-spec/plural-rules
|
|
6
6
|
CLDR_CATEGORY_KEYS = %w[zero one two few many other].freeze
|
|
7
7
|
PLURAL_KEY_SUFFIXES = Set.new CLDR_CATEGORY_KEYS
|
|
8
|
-
PLURAL_KEY_RE = /\.(?:#{CLDR_CATEGORY_KEYS *
|
|
8
|
+
PLURAL_KEY_RE = /\.(?:#{CLDR_CATEGORY_KEYS * "|"})$/
|
|
9
|
+
# Ref: https://unicode-org.github.io/cldr/ldml/tr35-numbers.html#Explicit_0_1_rules
|
|
10
|
+
EXPLICIT_0_1 = %w[0 1].freeze
|
|
9
11
|
|
|
10
12
|
def collapse_plural_nodes!(tree)
|
|
11
13
|
tree.leaves.map(&:parent).compact.uniq.each do |node|
|
|
12
14
|
children = node.children
|
|
13
15
|
next unless plural_forms?(children)
|
|
14
16
|
|
|
15
|
-
node.value
|
|
17
|
+
node.value = children.to_hash
|
|
16
18
|
node.children = nil
|
|
17
19
|
node.data.merge! children.first.data
|
|
18
20
|
end
|
|
@@ -56,6 +58,6 @@ module I18n::Tasks::PluralKeys
|
|
|
56
58
|
end
|
|
57
59
|
|
|
58
60
|
def plural_suffix?(key)
|
|
59
|
-
PLURAL_KEY_SUFFIXES.include?(key)
|
|
61
|
+
PLURAL_KEY_SUFFIXES.include?(key) || EXPLICIT_0_1.include?(key)
|
|
60
62
|
end
|
|
61
63
|
end
|
|
@@ -7,9 +7,9 @@ module I18n::Tasks
|
|
|
7
7
|
# 2. Resolved references -- all the used references in their fully resolved form.
|
|
8
8
|
# 3. Reference keys -- all the used reference keys.
|
|
9
9
|
def process_references(usages,
|
|
10
|
-
|
|
11
|
-
fail ArgumentError,
|
|
12
|
-
fail ArgumentError,
|
|
10
|
+
data_refs = merge_reference_trees(data_forest.select_keys { |_, node| node.reference? }))
|
|
11
|
+
fail ArgumentError, "usages must be a Data::Tree::Instance" unless usages.is_a?(Data::Tree::Siblings)
|
|
12
|
+
fail ArgumentError, "all_references must be a Data::Tree::Instance" unless data_refs.is_a?(Data::Tree::Siblings)
|
|
13
13
|
|
|
14
14
|
raw_refs = empty_forest
|
|
15
15
|
resolved_refs = empty_forest
|
|
@@ -90,7 +90,7 @@ module I18n::Tasks
|
|
|
90
90
|
on_leaves_merge: lambda do |node, other|
|
|
91
91
|
if node.value != other.value
|
|
92
92
|
log_warn(
|
|
93
|
-
|
|
93
|
+
"Conflicting references: " \
|
|
94
94
|
"#{node.full_key(root: false)} ⮕ #{node.value} in #{node.data[:locale]}, " \
|
|
95
95
|
"but ⮕ #{other.value} in #{other.data[:locale]}"
|
|
96
96
|
)
|
|
@@ -15,55 +15,59 @@ module I18n::Tasks::Reports
|
|
|
15
15
|
protected
|
|
16
16
|
|
|
17
17
|
def missing_type_info(type)
|
|
18
|
-
::I18n::Tasks::MissingKeys::MISSING_TYPES[type.to_s.sub(/\Amissing_/,
|
|
18
|
+
::I18n::Tasks::MissingKeys::MISSING_TYPES[type.to_s.sub(/\Amissing_/, "").to_sym]
|
|
19
19
|
end
|
|
20
20
|
|
|
21
21
|
def missing_title(forest)
|
|
22
|
-
"Missing translations (#{forest.leaves.count ||
|
|
22
|
+
"Missing translations (#{forest.leaves.count || "∅"})"
|
|
23
23
|
end
|
|
24
24
|
|
|
25
25
|
def inconsistent_interpolations_title(forest)
|
|
26
|
-
"Inconsistent interpolations (#{forest.leaves.count ||
|
|
26
|
+
"Inconsistent interpolations (#{forest.leaves.count || "∅"})"
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def reserved_interpolations_title(forest)
|
|
30
|
+
"Reserved interpolations (#{forest.leaves.count || "∅"})"
|
|
27
31
|
end
|
|
28
32
|
|
|
29
33
|
def unused_title(key_values)
|
|
30
|
-
"Unused keys (#{key_values.count ||
|
|
34
|
+
"Unused keys (#{key_values.count || "∅"})"
|
|
31
35
|
end
|
|
32
36
|
|
|
33
37
|
def eq_base_title(key_values, locale = base_locale)
|
|
34
|
-
"Same value as #{locale} (#{key_values.count ||
|
|
38
|
+
"Same value as #{locale} (#{key_values.count || "∅"})"
|
|
35
39
|
end
|
|
36
40
|
|
|
37
41
|
def used_title(keys_nodes, filter)
|
|
38
42
|
used_n = keys_nodes.map { |_k, node| node.data[:occurrences].size }.reduce(:+).to_i
|
|
39
|
-
"#{keys_nodes.size} key#{
|
|
40
|
-
"#{" (#{used_n} usage#{
|
|
43
|
+
"#{keys_nodes.size} key#{"s" if keys_nodes.size != 1}#{" matching '#{filter}'" if filter}" \
|
|
44
|
+
"#{" (#{used_n} usage#{"s" if used_n != 1})" if used_n.positive?}"
|
|
41
45
|
end
|
|
42
46
|
|
|
43
47
|
# Sort keys by their attributes in order
|
|
44
48
|
# @param [Hash] order e.g. {locale: :asc, type: :desc, key: :asc}
|
|
45
|
-
def sort_by_attr!(objects, order = {
|
|
49
|
+
def sort_by_attr!(objects, order = {locale: :asc, key: :asc})
|
|
46
50
|
order_keys = order.keys
|
|
47
51
|
objects.sort! do |a, b|
|
|
48
52
|
by = order_keys.detect { |k| a[k] != b[k] }
|
|
49
|
-
order[by] == :desc ? b[by] <=> a[by] : a[by] <=> b[by]
|
|
53
|
+
(order[by] == :desc) ? b[by] <=> a[by] : a[by] <=> b[by]
|
|
50
54
|
end
|
|
51
55
|
objects
|
|
52
56
|
end
|
|
53
57
|
|
|
54
58
|
def forest_to_attr(forest)
|
|
55
59
|
forest.keys(root: false).map do |key, node|
|
|
56
|
-
{
|
|
60
|
+
{key: key, value: node.value, type: node.data[:type], locale: node.root.key, data: node.data}
|
|
57
61
|
end
|
|
58
62
|
end
|
|
59
63
|
|
|
60
64
|
def format_locale(locale)
|
|
61
|
-
return
|
|
65
|
+
return "" unless locale
|
|
62
66
|
|
|
63
|
-
if locale.split(
|
|
64
|
-
|
|
67
|
+
if locale.split("+") == task.locales.sort
|
|
68
|
+
"all"
|
|
65
69
|
else
|
|
66
|
-
locale.tr
|
|
70
|
+
locale.tr "+", " "
|
|
67
71
|
end
|
|
68
72
|
end
|
|
69
73
|
|