i18n-tasks 0.9.33 → 1.0.12
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 +28 -11
- data/config/locales/en.yml +5 -3
- data/config/locales/ru.yml +1 -0
- data/i18n-tasks.gemspec +12 -6
- data/lib/i18n/tasks/base_task.rb +2 -1
- data/lib/i18n/tasks/cli.rb +27 -17
- data/lib/i18n/tasks/command/commander.rb +1 -0
- data/lib/i18n/tasks/command/commands/data.rb +8 -6
- data/lib/i18n/tasks/command/commands/eq_base.rb +2 -2
- data/lib/i18n/tasks/command/commands/health.rb +4 -3
- data/lib/i18n/tasks/command/commands/interpolations.rb +1 -1
- data/lib/i18n/tasks/command/commands/meta.rb +1 -1
- data/lib/i18n/tasks/command/commands/missing.rb +22 -9
- data/lib/i18n/tasks/command/commands/tree.rb +8 -6
- data/lib/i18n/tasks/command/commands/usages.rb +5 -4
- data/lib/i18n/tasks/command/dsl.rb +4 -4
- data/lib/i18n/tasks/command/option_parsers/enum.rb +2 -0
- data/lib/i18n/tasks/command/option_parsers/locale.rb +2 -1
- data/lib/i18n/tasks/command/options/common.rb +5 -0
- data/lib/i18n/tasks/command/options/data.rb +4 -1
- data/lib/i18n/tasks/command/options/locales.rb +5 -5
- data/lib/i18n/tasks/concurrent/cached_value.rb +2 -2
- data/lib/i18n/tasks/configuration.rb +17 -10
- data/lib/i18n/tasks/console_context.rb +1 -1
- data/lib/i18n/tasks/data/adapter/yaml_adapter.rb +9 -2
- data/lib/i18n/tasks/data/file_formats.rb +3 -1
- data/lib/i18n/tasks/data/file_system_base.rb +7 -6
- data/lib/i18n/tasks/data/router/conservative_router.rb +2 -1
- data/lib/i18n/tasks/data/router/pattern_router.rb +3 -1
- data/lib/i18n/tasks/data/tree/node.rb +6 -3
- data/lib/i18n/tasks/data/tree/nodes.rb +6 -7
- data/lib/i18n/tasks/data/tree/siblings.rb +10 -4
- data/lib/i18n/tasks/data/tree/traversal.rb +34 -11
- data/lib/i18n/tasks/html_keys.rb +4 -6
- data/lib/i18n/tasks/ignore_keys.rb +4 -3
- data/lib/i18n/tasks/interpolations.rb +10 -4
- data/lib/i18n/tasks/key_pattern_matching.rb +3 -2
- data/lib/i18n/tasks/locale_pathname.rb +1 -1
- data/lib/i18n/tasks/missing_keys.rb +4 -0
- data/lib/i18n/tasks/plural_keys.rb +5 -6
- data/lib/i18n/tasks/references.rb +4 -2
- data/lib/i18n/tasks/reports/base.rb +4 -3
- data/lib/i18n/tasks/reports/terminal.rb +8 -6
- data/lib/i18n/tasks/scanners/ast_matchers/base_matcher.rb +118 -0
- data/lib/i18n/tasks/scanners/ast_matchers/message_receivers_matcher.rb +91 -0
- data/lib/i18n/tasks/scanners/ast_matchers/rails_model_matcher.rb +69 -0
- data/lib/i18n/tasks/scanners/erb_ast_processor.rb +74 -0
- data/lib/i18n/tasks/scanners/erb_ast_scanner.rb +48 -0
- data/lib/i18n/tasks/scanners/file_scanner.rb +4 -3
- data/lib/i18n/tasks/scanners/files/caching_file_finder.rb +0 -3
- data/lib/i18n/tasks/scanners/files/file_finder.rb +3 -2
- data/lib/i18n/tasks/scanners/local_ruby_parser.rb +85 -0
- data/lib/i18n/tasks/scanners/occurrence_from_position.rb +3 -3
- data/lib/i18n/tasks/scanners/pattern_mapper.rb +1 -1
- data/lib/i18n/tasks/scanners/pattern_scanner.rb +8 -5
- data/lib/i18n/tasks/scanners/pattern_with_scope_scanner.rb +4 -2
- data/lib/i18n/tasks/scanners/relative_keys.rb +19 -4
- data/lib/i18n/tasks/scanners/results/occurrence.rb +17 -1
- data/lib/i18n/tasks/scanners/ruby_ast_call_finder.rb +9 -34
- data/lib/i18n/tasks/scanners/ruby_ast_scanner.rb +91 -154
- data/lib/i18n/tasks/scanners/ruby_key_literals.rb +4 -4
- data/lib/i18n/tasks/scanners/scanner_multiplexer.rb +2 -0
- data/lib/i18n/tasks/split_key.rb +3 -1
- data/lib/i18n/tasks/string_interpolation.rb +1 -0
- data/lib/i18n/tasks/translation.rb +3 -3
- data/lib/i18n/tasks/translators/base_translator.rb +5 -3
- data/lib/i18n/tasks/translators/deepl_translator.rb +10 -2
- data/lib/i18n/tasks/translators/google_translator.rb +2 -0
- data/lib/i18n/tasks/translators/yandex_translator.rb +2 -0
- data/lib/i18n/tasks/used_keys.rb +21 -14
- data/lib/i18n/tasks/version.rb +1 -1
- data/lib/i18n/tasks.rb +17 -7
- data/templates/config/i18n-tasks.yml +21 -1
- metadata +44 -15
@@ -9,7 +9,7 @@ module I18n::Tasks
|
|
9
9
|
include Command::DSL
|
10
10
|
|
11
11
|
DATA_FORMATS = %w[yaml json keys].freeze
|
12
|
-
OUT_FORMATS = ['terminal-table', *DATA_FORMATS, 'inspect'].freeze
|
12
|
+
OUT_FORMATS = ['terminal-table', *DATA_FORMATS, 'inspect', 'key-values'].freeze
|
13
13
|
|
14
14
|
format_arg = proc do |type, values|
|
15
15
|
default = values.first
|
@@ -64,6 +64,7 @@ module I18n::Tasks
|
|
64
64
|
# @return [I18n::Tasks::Data::Tree::Siblings]
|
65
65
|
def parse_forest(src, format)
|
66
66
|
fail CommandError, I18n.t('i18n_tasks.cmd.errors.pass_forest') unless src
|
67
|
+
|
67
68
|
if format == 'keys'
|
68
69
|
::I18n::Tasks::Data::Tree::Siblings.from_key_names parse_keys(src)
|
69
70
|
else
|
@@ -84,6 +85,8 @@ module I18n::Tasks
|
|
84
85
|
puts forest.inspect
|
85
86
|
when 'keys'
|
86
87
|
puts forest.key_names(root: true)
|
88
|
+
when 'key-values'
|
89
|
+
puts(forest.key_values(root: true).map { |kv| kv.join("\t") })
|
87
90
|
when *DATA_FORMATS
|
88
91
|
puts i18n.data.adapter_dump forest.to_hash(true), format
|
89
92
|
end
|
@@ -13,22 +13,22 @@ module I18n::Tasks
|
|
13
13
|
'--locales en,es,ru',
|
14
14
|
Array,
|
15
15
|
t('i18n_tasks.cmd.args.desc.locales_filter'),
|
16
|
-
parser:
|
17
|
-
default:
|
16
|
+
parser: OptionParsers::Locale::ListParser,
|
17
|
+
default: 'all',
|
18
18
|
consume_positional: true
|
19
19
|
|
20
20
|
arg :locale,
|
21
21
|
'-l',
|
22
22
|
'--locale en',
|
23
23
|
t('i18n_tasks.cmd.args.desc.locale'),
|
24
|
-
parser:
|
24
|
+
parser: OptionParsers::Locale::Parser,
|
25
25
|
default: 'base'
|
26
26
|
|
27
27
|
arg :locale_to_translate_from,
|
28
28
|
'-f',
|
29
29
|
'--from en',
|
30
30
|
t('i18n_tasks.cmd.args.desc.locale_to_translate_from'),
|
31
|
-
parser:
|
31
|
+
parser: OptionParsers::Locale::Parser,
|
32
32
|
default: 'base'
|
33
33
|
|
34
34
|
TRANSLATION_BACKENDS = %w[google deepl].freeze
|
@@ -36,7 +36,7 @@ module I18n::Tasks
|
|
36
36
|
'-b',
|
37
37
|
'--backend BACKEND',
|
38
38
|
t('i18n_tasks.cmd.args.desc.translation_backend'),
|
39
|
-
parser:
|
39
|
+
parser: OptionParsers::Locale::Parser,
|
40
40
|
default: TRANSLATION_BACKENDS[0]
|
41
41
|
end
|
42
42
|
end
|
@@ -1,7 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require 'i18n/tasks/concurrent/cached_value'
|
4
|
-
|
5
3
|
module I18n::Tasks::Concurrent
|
6
4
|
# A thread-safe memoized value.
|
7
5
|
# The given computation is guaranteed to be invoked at most once.
|
@@ -19,8 +17,10 @@ module I18n::Tasks::Concurrent
|
|
19
17
|
# @return [Object] Result of the computation.
|
20
18
|
def get
|
21
19
|
return get_result_volatile unless get_result_volatile == NULL
|
20
|
+
|
22
21
|
@mutex.synchronize do
|
23
22
|
next unless get_result_volatile == NULL
|
23
|
+
|
24
24
|
set_result_volatile @computation.call
|
25
25
|
@computation = nil
|
26
26
|
end
|
@@ -2,10 +2,10 @@
|
|
2
2
|
|
3
3
|
module I18n::Tasks::Configuration # rubocop:disable Metrics/ModuleLength
|
4
4
|
DEFAULTS = {
|
5
|
-
base_locale:
|
5
|
+
base_locale: 'en',
|
6
6
|
internal_locale: 'en',
|
7
|
-
search:
|
8
|
-
data:
|
7
|
+
search: ::I18n::Tasks::UsedKeys::SEARCH_DEFAULTS,
|
8
|
+
data: ::I18n::Tasks::Data::DATA_DEFAULTS
|
9
9
|
}.freeze
|
10
10
|
|
11
11
|
# i18n-tasks config (defaults + config/i18n-tasks.yml)
|
@@ -20,7 +20,7 @@ module I18n::Tasks::Configuration # rubocop:disable Metrics/ModuleLength
|
|
20
20
|
].freeze
|
21
21
|
|
22
22
|
def file_config
|
23
|
-
file = CONFIG_FILES.detect { |f| File.exist?(f) }
|
23
|
+
file = @config_override || CONFIG_FILES.detect { |f| File.exist?(f) }
|
24
24
|
# rubocop:disable Security/Eval
|
25
25
|
config = file && YAML.load(eval(Erubi::Engine.new(File.read(file, encoding: 'UTF-8')).src))
|
26
26
|
# rubocop:enable Security/Eval
|
@@ -30,6 +30,13 @@ module I18n::Tasks::Configuration # rubocop:disable Metrics/ModuleLength
|
|
30
30
|
warn_deprecated 'Please move relative_roots under search in config/i18n-tasks.yml.'
|
31
31
|
c[:search][:relative_roots] = c.delete(:relative_roots)
|
32
32
|
end
|
33
|
+
|
34
|
+
if c.dig(:search, :exclude_method_name_paths)
|
35
|
+
warn_deprecated(
|
36
|
+
'Please rename exclude_method_name_paths to relative_exclude_method_name_paths in config/i18n-tasks.yml.'
|
37
|
+
)
|
38
|
+
c[:search][:relative_exclude_method_name_paths] = c[:search].delete(:exclude_method_name_paths)
|
39
|
+
end
|
33
40
|
end
|
34
41
|
else
|
35
42
|
{}.with_indifferent_access
|
@@ -44,12 +51,10 @@ module I18n::Tasks::Configuration # rubocop:disable Metrics/ModuleLength
|
|
44
51
|
# data config
|
45
52
|
# @return [Hash<adapter: String, options: Hash>]
|
46
53
|
def data_config
|
47
|
-
@config_sections[:data] ||=
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
}
|
52
|
-
end
|
54
|
+
@config_sections[:data] ||= {
|
55
|
+
adapter: data.class.name,
|
56
|
+
config: data.config
|
57
|
+
}
|
53
58
|
end
|
54
59
|
|
55
60
|
# translation config
|
@@ -59,6 +64,8 @@ module I18n::Tasks::Configuration # rubocop:disable Metrics/ModuleLength
|
|
59
64
|
conf = (config[:translation] || {}).with_indifferent_access
|
60
65
|
conf[:google_translate_api_key] = ENV['GOOGLE_TRANSLATE_API_KEY'] if ENV.key?('GOOGLE_TRANSLATE_API_KEY')
|
61
66
|
conf[:deepl_api_key] = ENV['DEEPL_AUTH_KEY'] if ENV.key?('DEEPL_AUTH_KEY')
|
67
|
+
conf[:deepl_host] = ENV['DEEPL_HOST'] if ENV.key?('DEEPL_HOST')
|
68
|
+
conf[:deepl_version] = ENV['DEEPL_VERSION'] if ENV.key?('DEEPL_VERSION')
|
62
69
|
conf[:yandex_api_key] = ENV['YANDEX_API_KEY'] if ENV.key?('YANDEX_API_KEY')
|
63
70
|
conf
|
64
71
|
end
|
@@ -34,7 +34,7 @@ module I18n::Tasks
|
|
34
34
|
end
|
35
35
|
|
36
36
|
def guide
|
37
|
-
Rainbow('i18n-tasks IRB Quick Start guide').green.bright
|
37
|
+
"#{Rainbow('i18n-tasks IRB Quick Start guide').green.bright}\n#{<<~TEXT}"
|
38
38
|
#{Rainbow('Data as trees').yellow}
|
39
39
|
tree(locale)
|
40
40
|
used_tree(key_filter: nil, strict: nil)
|
@@ -5,11 +5,13 @@ module I18n::Tasks
|
|
5
5
|
module Data
|
6
6
|
module Adapter
|
7
7
|
module YamlAdapter
|
8
|
+
EMOJI_REGEX = /\\u[\da-f]{8}/i.freeze
|
9
|
+
|
8
10
|
class << self
|
9
11
|
# @return [Hash] locale tree
|
10
12
|
def parse(str, options)
|
11
13
|
if YAML.method(:load).arity.abs == 2
|
12
|
-
YAML.
|
14
|
+
YAML.safe_load(str, **(options || {}), permitted_classes: [Symbol], aliases: true)
|
13
15
|
else
|
14
16
|
# older jruby and rbx 2.2.7 do not accept options
|
15
17
|
YAML.load(str)
|
@@ -18,7 +20,12 @@ module I18n::Tasks
|
|
18
20
|
|
19
21
|
# @return [String]
|
20
22
|
def dump(tree, options)
|
21
|
-
tree.to_yaml(options || {})
|
23
|
+
restore_emojis(tree.to_yaml(options || {}))
|
24
|
+
end
|
25
|
+
|
26
|
+
# @return [String]
|
27
|
+
def restore_emojis(yaml)
|
28
|
+
yaml.gsub(EMOJI_REGEX) { |m| [m[-8..].to_i(16)].pack('U') }
|
22
29
|
end
|
23
30
|
end
|
24
31
|
end
|
@@ -53,12 +53,14 @@ module I18n
|
|
53
53
|
content = adapter_dump(hash, adapter)
|
54
54
|
# Ignore unchanged data
|
55
55
|
return if File.file?(path) && content == read_file(path)
|
56
|
+
|
56
57
|
::FileUtils.mkpath(File.dirname(path))
|
57
|
-
::File.
|
58
|
+
::File.write(path, content)
|
58
59
|
end
|
59
60
|
|
60
61
|
def normalized?(path, tree)
|
61
62
|
return false unless File.file?(path)
|
63
|
+
|
62
64
|
read_file(path) == adapter_dump(tree.to_hash(true), self.class.adapter_name_for_path(path))
|
63
65
|
end
|
64
66
|
|
@@ -12,11 +12,12 @@ module I18n::Tasks
|
|
12
12
|
include ::I18n::Tasks::Data::FileFormats
|
13
13
|
include ::I18n::Tasks::Logging
|
14
14
|
|
15
|
-
|
16
|
-
|
15
|
+
attr_accessor :locales
|
16
|
+
attr_reader :config, :base_locale
|
17
|
+
attr_writer :router
|
17
18
|
|
18
19
|
DEFAULTS = {
|
19
|
-
read:
|
20
|
+
read: ['config/locales/%{locale}.yml'],
|
20
21
|
write: ['config/locales/%{locale}.yml']
|
21
22
|
}.freeze
|
22
23
|
|
@@ -131,11 +132,12 @@ module I18n::Tasks
|
|
131
132
|
def t(key, locale)
|
132
133
|
tree = self[locale.to_s]
|
133
134
|
return unless tree
|
135
|
+
|
134
136
|
tree[locale][key].try(:value_or_children_hash)
|
135
137
|
end
|
136
138
|
|
137
139
|
def config=(config)
|
138
|
-
@config = DEFAULTS.deep_merge((config || {}).
|
140
|
+
@config = DEFAULTS.deep_merge((config || {}).compact)
|
139
141
|
reload
|
140
142
|
end
|
141
143
|
|
@@ -159,7 +161,6 @@ module I18n::Tasks
|
|
159
161
|
router_class.new(self, @config.merge(base_locale: base_locale, locales: locales))
|
160
162
|
end
|
161
163
|
end
|
162
|
-
attr_writer :router
|
163
164
|
|
164
165
|
protected
|
165
166
|
|
@@ -188,7 +189,7 @@ module I18n::Tasks
|
|
188
189
|
The following unquoted YAML keys result in a nil key:
|
189
190
|
#{%w[null Null NULL ~].join(', ')}
|
190
191
|
See http://yaml.org/type/null.html
|
191
|
-
TEXT
|
192
|
+
TEXT
|
192
193
|
elsif value.is_a?(Hash)
|
193
194
|
filter_nil_keys! path, value, suffix + [key]
|
194
195
|
end
|
@@ -13,8 +13,9 @@ module I18n::Tasks
|
|
13
13
|
super
|
14
14
|
end
|
15
15
|
|
16
|
-
def route(locale, forest, &block)
|
16
|
+
def route(locale, forest, &block) # rubocop:disable Metrics/AbcSize
|
17
17
|
return to_enum(:route, locale, forest) unless block
|
18
|
+
|
18
19
|
out = Hash.new { |hash, key| hash[key] = Set.new }
|
19
20
|
not_found = Set.new
|
20
21
|
forest.keys do |key, _node|
|
@@ -10,6 +10,7 @@ module I18n::Tasks
|
|
10
10
|
include ::I18n::Tasks::KeyPatternMatching
|
11
11
|
|
12
12
|
attr_reader :routes
|
13
|
+
|
13
14
|
# @option data_config write [Array] of routes
|
14
15
|
# @example
|
15
16
|
# {write:
|
@@ -29,6 +30,7 @@ module I18n::Tasks
|
|
29
30
|
# @return [Hash] mapping of destination => [ [key, value], ... ]
|
30
31
|
def route(locale, forest, &block)
|
31
32
|
return to_enum(:route, locale, forest) unless block
|
33
|
+
|
32
34
|
locale = locale.to_s
|
33
35
|
out = {}
|
34
36
|
forest.keys do |key, _node|
|
@@ -36,7 +38,7 @@ module I18n::Tasks
|
|
36
38
|
if pattern
|
37
39
|
key_match = $~
|
38
40
|
path = format(path, locale: locale)
|
39
|
-
path.gsub!(/\\\d+/) { |m| key_match[m[1
|
41
|
+
path.gsub!(/\\\d+/) { |m| key_match[m[1..].to_i] }
|
40
42
|
(out[path] ||= Set.new) << "#{locale}.#{key}"
|
41
43
|
else
|
42
44
|
fail CommandError, "Cannot route key #{key}. Routes are #{@routes_config.inspect}"
|
@@ -18,7 +18,7 @@ module I18n::Tasks::Data::Tree
|
|
18
18
|
@data = data
|
19
19
|
@parent = parent
|
20
20
|
@warn_about_add_children_to_leaf = warn_about_add_children_to_leaf
|
21
|
-
self.children =
|
21
|
+
self.children = children if children
|
22
22
|
end
|
23
23
|
# rubocop:enable Metrics/ParameterLists
|
24
24
|
|
@@ -48,6 +48,7 @@ module I18n::Tasks::Data::Tree
|
|
48
48
|
|
49
49
|
def each(&block)
|
50
50
|
return to_enum(:each) { 1 } unless block
|
51
|
+
|
51
52
|
block.yield(self)
|
52
53
|
self
|
53
54
|
end
|
@@ -112,6 +113,7 @@ module I18n::Tasks::Data::Tree
|
|
112
113
|
|
113
114
|
def walk_to_root(&visitor)
|
114
115
|
return to_enum(:walk_to_root) unless visitor
|
116
|
+
|
115
117
|
visitor.yield self
|
116
118
|
parent.walk_to_root(&visitor) if parent?
|
117
119
|
end
|
@@ -124,6 +126,7 @@ module I18n::Tasks::Data::Tree
|
|
124
126
|
|
125
127
|
def walk_from_root(&visitor)
|
126
128
|
return to_enum(:walk_from_root) unless visitor
|
129
|
+
|
127
130
|
walk_to_root.reverse_each do |node|
|
128
131
|
visitor.yield node
|
129
132
|
end
|
@@ -165,11 +168,11 @@ module I18n::Tasks::Data::Tree
|
|
165
168
|
label = if key.nil?
|
166
169
|
Rainbow('∅').faint
|
167
170
|
else
|
168
|
-
[Rainbow(key).color(1 + level % 15),
|
171
|
+
[Rainbow(key).color(1 + (level % 15)),
|
169
172
|
(": #{format_value_for_inspect(value)}" if leaf?),
|
170
173
|
(" #{data}" if data?)].compact.join
|
171
174
|
end
|
172
|
-
[' ' * level, label, ("\n
|
175
|
+
[' ' * level, label, ("\n#{children.map { |c| c.inspect(level + 1) }.join("\n")}" if children?)].compact.join
|
173
176
|
end
|
174
177
|
|
175
178
|
def format_value_for_inspect(value)
|
@@ -31,13 +31,11 @@ module I18n::Tasks::Data::Tree
|
|
31
31
|
end
|
32
32
|
|
33
33
|
def to_hash(sort = false)
|
34
|
-
(@hash ||= {})[sort] ||=
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
end.map { |node| node.to_hash(sort) }.reduce({}, :deep_merge!)
|
40
|
-
end
|
34
|
+
(@hash ||= {})[sort] ||= if sort
|
35
|
+
sort_by(&:key)
|
36
|
+
else
|
37
|
+
self
|
38
|
+
end.map { |node| node.to_hash(sort) }.reduce({}, :deep_merge!)
|
41
39
|
end
|
42
40
|
|
43
41
|
delegate :to_json, to: :to_hash
|
@@ -80,6 +78,7 @@ module I18n::Tasks::Data::Tree
|
|
80
78
|
|
81
79
|
def children(&block)
|
82
80
|
return to_enum(:children) { map { |c| c.children ? c.children.size : 0 }.reduce(:+) } unless block
|
81
|
+
|
83
82
|
each do |node|
|
84
83
|
node.children.each(&block) if node.children?
|
85
84
|
end
|
@@ -3,7 +3,6 @@
|
|
3
3
|
require 'set'
|
4
4
|
require 'i18n/tasks/split_key'
|
5
5
|
require 'i18n/tasks/data/tree/nodes'
|
6
|
-
require 'i18n/tasks/data/tree/node'
|
7
6
|
|
8
7
|
module I18n::Tasks::Data::Tree
|
9
8
|
# Siblings represents a subtree sharing a common parent
|
@@ -50,7 +49,7 @@ module I18n::Tasks::Data::Tree
|
|
50
49
|
next
|
51
50
|
end
|
52
51
|
match = $~
|
53
|
-
new_key = to_pattern.gsub(/\\\d+/) { |m| match[m[1
|
52
|
+
new_key = to_pattern.gsub(/\\\d+/) { |m| match[m[1..].to_i] }
|
54
53
|
old_key_to_new_key[full_key] = new_key
|
55
54
|
moved_forest.merge!(Siblings.new.tap do |forest|
|
56
55
|
forest[[(node.root.try(:key) unless root), new_key].compact.join('.')] =
|
@@ -62,6 +61,7 @@ module I18n::Tasks::Data::Tree
|
|
62
61
|
# TODO: support nested references better
|
63
62
|
nodes do |node|
|
64
63
|
next unless node.reference?
|
64
|
+
|
65
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
|
@@ -92,6 +92,7 @@ module I18n::Tasks::Data::Tree
|
|
92
92
|
# add or replace node by full key
|
93
93
|
def set(full_key, node)
|
94
94
|
fail 'value should be a I18n::Tasks::Data::Tree::Node' unless node.is_a?(Node)
|
95
|
+
|
95
96
|
key_part, rest = split_key(full_key, 2)
|
96
97
|
child = key_to_node[key_part]
|
97
98
|
|
@@ -131,6 +132,7 @@ module I18n::Tasks::Data::Tree
|
|
131
132
|
def append!(nodes)
|
132
133
|
nodes = nodes.map do |node|
|
133
134
|
fail "already has a child with key '#{node.key}'" if key_to_node.key?(node.key)
|
135
|
+
|
134
136
|
key_to_node[node.key] = (node.parent == parent ? node : node.derive(parent: parent))
|
135
137
|
end
|
136
138
|
super(nodes)
|
@@ -172,16 +174,18 @@ module I18n::Tasks::Data::Tree
|
|
172
174
|
|
173
175
|
def set_root_key!(new_key, data = nil)
|
174
176
|
return self if empty?
|
177
|
+
|
175
178
|
rename_key first.key, new_key
|
176
179
|
leaves { |node| node.data.merge! data } if data
|
177
180
|
self
|
178
181
|
end
|
179
182
|
|
180
183
|
# @param on_leaves_merge [Proc] invoked when a leaf is merged with another leaf
|
181
|
-
def merge_node!(node, on_leaves_merge: nil) # rubocop:disable Metrics/AbcSize
|
184
|
+
def merge_node!(node, on_leaves_merge: nil) # rubocop:disable Metrics/AbcSize
|
182
185
|
if key_to_node.key?(node.key)
|
183
186
|
our = key_to_node[node.key]
|
184
187
|
return if our == node
|
188
|
+
|
185
189
|
our.value = node.value if node.leaf?
|
186
190
|
our.data.merge!(node.data) if node.data?
|
187
191
|
if node.children?
|
@@ -262,7 +266,7 @@ module I18n::Tasks::Data::Tree
|
|
262
266
|
build_forest(warn_about_add_children_to_leaf: false) do |forest|
|
263
267
|
key_occurrences.each do |key_occurrence|
|
264
268
|
forest[key_occurrence.key] = ::I18n::Tasks::Data::Tree::Node.new(
|
265
|
-
key:
|
269
|
+
key: split_key(key_occurrence.key).last,
|
266
270
|
data: { occurrences: key_occurrence.occurrences }
|
267
271
|
)
|
268
272
|
end
|
@@ -273,6 +277,7 @@ module I18n::Tasks::Data::Tree
|
|
273
277
|
build_forest(opts) do |forest|
|
274
278
|
key_attrs.each do |(full_key, attr)|
|
275
279
|
fail "Invalid key #{full_key.inspect}" if full_key.end_with?('.')
|
280
|
+
|
276
281
|
node = ::I18n::Tasks::Data::Tree::Node.new(**attr.merge(key: split_key(full_key).last))
|
277
282
|
yield(full_key, node) if block
|
278
283
|
forest[full_key] = node
|
@@ -295,6 +300,7 @@ module I18n::Tasks::Data::Tree
|
|
295
300
|
def from_nested_hash(hash, opts = {})
|
296
301
|
parse_parent_opt!(opts)
|
297
302
|
fail I18n::Tasks::CommandError, "invalid tree #{hash.inspect}" unless hash.respond_to?(:map)
|
303
|
+
|
298
304
|
opts[:nodes] = hash.map { |key, value| Node.from_key_value key, value }
|
299
305
|
Siblings.new(opts)
|
300
306
|
end
|
@@ -12,6 +12,7 @@ module I18n::Tasks
|
|
12
12
|
|
13
13
|
def leaves(&visitor)
|
14
14
|
return to_enum(:leaves) unless visitor
|
15
|
+
|
15
16
|
nodes do |node|
|
16
17
|
visitor.yield(node) if node.leaf?
|
17
18
|
end
|
@@ -20,6 +21,7 @@ module I18n::Tasks
|
|
20
21
|
|
21
22
|
def levels(&block)
|
22
23
|
return to_enum(:levels) unless block
|
24
|
+
|
23
25
|
nodes = to_nodes
|
24
26
|
unless nodes.empty?
|
25
27
|
block.yield nodes
|
@@ -35,6 +37,7 @@ module I18n::Tasks
|
|
35
37
|
|
36
38
|
def breadth_first(&visitor)
|
37
39
|
return to_enum(:breadth_first) unless visitor
|
40
|
+
|
38
41
|
levels do |nodes|
|
39
42
|
nodes.each { |node| visitor.yield(node) }
|
40
43
|
end
|
@@ -43,9 +46,11 @@ module I18n::Tasks
|
|
43
46
|
|
44
47
|
def depth_first(&visitor)
|
45
48
|
return to_enum(:depth_first) unless visitor
|
49
|
+
|
46
50
|
each do |node|
|
47
51
|
visitor.yield node
|
48
52
|
next unless node.children?
|
53
|
+
|
49
54
|
node.children.each do |child|
|
50
55
|
child.depth_first(&visitor)
|
51
56
|
end
|
@@ -56,6 +61,7 @@ module I18n::Tasks
|
|
56
61
|
# @option root include root in full key
|
57
62
|
def keys(root: false, &visitor)
|
58
63
|
return to_enum(:keys, root: root) unless visitor
|
64
|
+
|
59
65
|
leaves { |node| visitor.yield(node.full_key(root: root), node) }
|
60
66
|
self
|
61
67
|
end
|
@@ -70,13 +76,13 @@ module I18n::Tasks
|
|
70
76
|
|
71
77
|
def root_key_values(sort = false)
|
72
78
|
result = keys(root: false).map { |key, node| [node.root.key, key, node.value] }
|
73
|
-
result.sort! { |a, b| a[0]
|
79
|
+
result.sort! { |a, b| a[0] == b[0] ? a[1] <=> b[1] : a[0] <=> b[0] } if sort
|
74
80
|
result
|
75
81
|
end
|
76
82
|
|
77
83
|
def root_key_value_data(sort = false)
|
78
84
|
result = keys(root: false).map { |key, node| [node.root.key, key, node.value, node.data] }
|
79
|
-
result.sort! { |a, b| a[0]
|
85
|
+
result.sort! { |a, b| a[0] == b[0] ? a[1] <=> b[1] : a[0] <=> b[0] } if sort
|
80
86
|
result
|
81
87
|
end
|
82
88
|
|
@@ -88,6 +94,7 @@ module I18n::Tasks
|
|
88
94
|
tree = Siblings.new
|
89
95
|
each do |node|
|
90
96
|
next unless block.yield(node)
|
97
|
+
|
91
98
|
tree.append! node.derive(
|
92
99
|
parent: tree.parent,
|
93
100
|
children: (node.children.select_nodes(&block).to_a if node.children)
|
@@ -163,22 +170,38 @@ module I18n::Tasks
|
|
163
170
|
value_proc ||= proc do |node|
|
164
171
|
node_value = node.value
|
165
172
|
next node_value if node.reference?
|
173
|
+
|
166
174
|
human_key = ActiveSupport::Inflector.humanize(node.key.to_s)
|
167
175
|
full_key = node.full_key
|
168
176
|
default = (node.data[:occurrences] || []).detect { |o| o.default_arg.presence }.try(:default_arg)
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
177
|
+
if default.is_a?(Hash)
|
178
|
+
default.each_with_object({}) do |(k, v), h|
|
179
|
+
h[k] = StringInterpolation.interpolate_soft(
|
180
|
+
val_pattern,
|
181
|
+
value: node_value,
|
182
|
+
human_key: human_key,
|
183
|
+
key: full_key,
|
184
|
+
default: v,
|
185
|
+
value_or_human_key: node_value.presence || human_key,
|
186
|
+
value_or_default_or_human_key: node_value.presence || v || human_key
|
187
|
+
)
|
188
|
+
end
|
189
|
+
else
|
190
|
+
StringInterpolation.interpolate_soft(
|
191
|
+
val_pattern,
|
192
|
+
value: node_value,
|
193
|
+
human_key: human_key,
|
194
|
+
key: full_key,
|
195
|
+
default: default,
|
196
|
+
value_or_human_key: node_value.presence || human_key,
|
197
|
+
value_or_default_or_human_key: node_value.presence || default || human_key
|
198
|
+
)
|
199
|
+
end
|
178
200
|
end
|
179
201
|
pattern_re = I18n::Tasks::KeyPatternMatching.compile_key_pattern(key_pattern) if key_pattern.present?
|
180
202
|
keys.each do |key, node|
|
181
203
|
next if pattern_re && key !~ pattern_re
|
204
|
+
|
182
205
|
node.value = value_proc.call(node)
|
183
206
|
end
|
184
207
|
self
|
data/lib/i18n/tasks/html_keys.rb
CHANGED
@@ -2,15 +2,13 @@
|
|
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/.freeze
|
6
|
+
MAYBE_PLURAL_HTML_KEY_PATTERN = /[.\-_]html\.[^.]+\z/.freeze
|
7
7
|
|
8
8
|
def html_key?(full_key, locale)
|
9
|
-
# rubocop:disable Style/DoubleNegation
|
10
9
|
!!(full_key =~ HTML_KEY_PATTERN ||
|
11
|
-
full_key =~ MAYBE_PLURAL_HTML_KEY_PATTERN &&
|
12
|
-
depluralize_key(split_key(full_key, 2)[1], locale) =~ HTML_KEY_PATTERN)
|
13
|
-
# rubocop:enable Style/DoubleNegation
|
10
|
+
(full_key =~ MAYBE_PLURAL_HTML_KEY_PATTERN &&
|
11
|
+
depluralize_key(split_key(full_key, 2)[1], locale) =~ HTML_KEY_PATTERN))
|
14
12
|
end
|
15
13
|
end
|
16
14
|
end
|
@@ -17,12 +17,13 @@ module I18n::Tasks::IgnoreKeys
|
|
17
17
|
@ignore_patterns[type][locale] ||= begin
|
18
18
|
global = ignore_config.presence || []
|
19
19
|
type_ignore = ignore_config(type).presence || []
|
20
|
-
patterns =
|
20
|
+
patterns = case type_ignore
|
21
|
+
when Array
|
21
22
|
global + type_ignore
|
22
|
-
|
23
|
+
when Hash
|
23
24
|
# ignore per locale
|
24
25
|
global + (type_ignore['all'] || []) +
|
25
|
-
|
26
|
+
type_ignore.select { |k, _v| k.to_s =~ /\b#{locale}\b/ }.values.flatten(1).compact
|
26
27
|
end
|
27
28
|
compile_patterns_re patterns
|
28
29
|
end
|
@@ -2,20 +2,26 @@
|
|
2
2
|
|
3
3
|
module I18n::Tasks
|
4
4
|
module Interpolations
|
5
|
-
|
5
|
+
class << self
|
6
|
+
attr_accessor :variable_regex
|
7
|
+
end
|
8
|
+
@variable_regex = /%{[^}]+}/.freeze
|
6
9
|
|
7
10
|
def inconsistent_interpolations(locales: nil, base_locale: nil) # rubocop:disable Metrics/AbcSize
|
8
11
|
locales ||= self.locales
|
9
12
|
base_locale ||= self.base_locale
|
10
13
|
result = empty_forest
|
14
|
+
variable_regex = I18n::Tasks::Interpolations.variable_regex
|
11
15
|
|
12
16
|
data[base_locale].key_values.each do |key, value|
|
13
17
|
next if !value.is_a?(String) || ignore_key?(key, :inconsistent_interpolations)
|
14
|
-
|
18
|
+
|
19
|
+
base_vars = Set.new(value.scan(variable_regex))
|
15
20
|
(locales - [base_locale]).each do |current_locale|
|
16
21
|
node = data[current_locale].first.children[key]
|
17
|
-
next unless node&.value
|
18
|
-
|
22
|
+
next unless node&.value.is_a?(String)
|
23
|
+
|
24
|
+
if base_vars != Set.new(node.value.scan(variable_regex))
|
19
25
|
result.merge!(node.walk_to_root.reduce(nil) { |c, p| [p.derive(children: c)] })
|
20
26
|
end
|
21
27
|
end
|
@@ -5,7 +5,7 @@ require 'strscan'
|
|
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/.freeze
|
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
|
-
/(?:#{
|
16
|
+
/(?:#{key_patterns.map { |p| compile_key_pattern p } * '|'})/m
|
17
17
|
end
|
18
18
|
end
|
19
19
|
|
@@ -24,6 +24,7 @@ module I18n::Tasks::KeyPatternMatching
|
|
24
24
|
# { a, b.c } match any in set, can use : and *, match is captured
|
25
25
|
def compile_key_pattern(key_pattern)
|
26
26
|
return key_pattern if key_pattern.is_a?(Regexp)
|
27
|
+
|
27
28
|
/\A#{key_pattern_re_body(key_pattern)}\z/
|
28
29
|
end
|
29
30
|
|