i18n-tasks 0.9.31 → 0.9.35

Sign up to get free protection for your applications and to get access to all the features.
Files changed (61) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +7 -7
  3. data/config/locales/en.yml +1 -0
  4. data/i18n-tasks.gemspec +4 -4
  5. data/lib/i18n/tasks/base_task.rb +2 -1
  6. data/lib/i18n/tasks/cli.rb +26 -17
  7. data/lib/i18n/tasks/command/commands/data.rb +8 -6
  8. data/lib/i18n/tasks/command/commands/eq_base.rb +2 -2
  9. data/lib/i18n/tasks/command/commands/health.rb +4 -3
  10. data/lib/i18n/tasks/command/commands/interpolations.rb +1 -1
  11. data/lib/i18n/tasks/command/commands/meta.rb +1 -1
  12. data/lib/i18n/tasks/command/commands/missing.rb +7 -6
  13. data/lib/i18n/tasks/command/commands/tree.rb +8 -6
  14. data/lib/i18n/tasks/command/commands/usages.rb +5 -4
  15. data/lib/i18n/tasks/command/dsl.rb +4 -4
  16. data/lib/i18n/tasks/command/option_parsers/enum.rb +2 -0
  17. data/lib/i18n/tasks/command/option_parsers/locale.rb +2 -1
  18. data/lib/i18n/tasks/command/options/common.rb +6 -0
  19. data/lib/i18n/tasks/command/options/data.rb +1 -0
  20. data/lib/i18n/tasks/command/options/locales.rb +5 -5
  21. data/lib/i18n/tasks/concurrent/cached_value.rb +2 -0
  22. data/lib/i18n/tasks/configuration.rb +7 -5
  23. data/lib/i18n/tasks/console_context.rb +1 -1
  24. data/lib/i18n/tasks/data/file_formats.rb +2 -0
  25. data/lib/i18n/tasks/data/file_system_base.rb +7 -6
  26. data/lib/i18n/tasks/data/router/conservative_router.rb +2 -1
  27. data/lib/i18n/tasks/data/router/pattern_router.rb +2 -0
  28. data/lib/i18n/tasks/data/tree/node.rb +5 -2
  29. data/lib/i18n/tasks/data/tree/nodes.rb +1 -0
  30. data/lib/i18n/tasks/data/tree/siblings.rb +9 -2
  31. data/lib/i18n/tasks/data/tree/traversal.rb +11 -2
  32. data/lib/i18n/tasks/html_keys.rb +2 -4
  33. data/lib/i18n/tasks/ignore_keys.rb +4 -3
  34. data/lib/i18n/tasks/interpolations.rb +4 -2
  35. data/lib/i18n/tasks/key_pattern_matching.rb +3 -2
  36. data/lib/i18n/tasks/missing_keys.rb +34 -7
  37. data/lib/i18n/tasks/plural_keys.rb +6 -1
  38. data/lib/i18n/tasks/references.rb +2 -0
  39. data/lib/i18n/tasks/reports/base.rb +3 -2
  40. data/lib/i18n/tasks/reports/terminal.rb +6 -4
  41. data/lib/i18n/tasks/scanners/file_scanner.rb +4 -3
  42. data/lib/i18n/tasks/scanners/files/caching_file_finder.rb +0 -3
  43. data/lib/i18n/tasks/scanners/files/file_finder.rb +3 -2
  44. data/lib/i18n/tasks/scanners/occurrence_from_position.rb +3 -3
  45. data/lib/i18n/tasks/scanners/pattern_scanner.rb +7 -4
  46. data/lib/i18n/tasks/scanners/pattern_with_scope_scanner.rb +4 -2
  47. data/lib/i18n/tasks/scanners/relative_keys.rb +18 -3
  48. data/lib/i18n/tasks/scanners/ruby_ast_scanner.rb +16 -13
  49. data/lib/i18n/tasks/scanners/ruby_key_literals.rb +3 -3
  50. data/lib/i18n/tasks/scanners/scanner_multiplexer.rb +2 -0
  51. data/lib/i18n/tasks/split_key.rb +2 -0
  52. data/lib/i18n/tasks/string_interpolation.rb +1 -0
  53. data/lib/i18n/tasks/translation.rb +3 -3
  54. data/lib/i18n/tasks/translators/base_translator.rb +4 -2
  55. data/lib/i18n/tasks/translators/deepl_translator.rb +16 -3
  56. data/lib/i18n/tasks/translators/google_translator.rb +2 -0
  57. data/lib/i18n/tasks/translators/yandex_translator.rb +2 -0
  58. data/lib/i18n/tasks/used_keys.rb +18 -13
  59. data/lib/i18n/tasks/version.rb +1 -1
  60. data/templates/config/i18n-tasks.yml +10 -1
  61. metadata +10 -7
@@ -12,11 +12,12 @@ module I18n::Tasks
12
12
  include ::I18n::Tasks::Data::FileFormats
13
13
  include ::I18n::Tasks::Logging
14
14
 
15
- attr_reader :config, :base_locale, :locales
16
- attr_writer :locales
15
+ attr_accessor :locales
16
+ attr_reader :config, :base_locale
17
+ attr_writer :router
17
18
 
18
19
  DEFAULTS = {
19
- read: ['config/locales/%{locale}.yml'],
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 || {}).reject { |_k, v| v.nil? })
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|
@@ -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 = (children if 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
@@ -169,7 +172,7 @@ module I18n::Tasks::Data::Tree
169
172
  (": #{format_value_for_inspect(value)}" if leaf?),
170
173
  (" #{data}" if data?)].compact.join
171
174
  end
172
- [' ' * level, label, ("\n" + children.map { |c| c.inspect(level + 1) }.join("\n") if children?)].compact.join
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)
@@ -80,6 +80,7 @@ module I18n::Tasks::Data::Tree
80
80
 
81
81
  def children(&block)
82
82
  return to_enum(:children) { map { |c| c.children ? c.children.size : 0 }.reduce(:+) } unless block
83
+
83
84
  each do |node|
84
85
  node.children.each(&block) if node.children?
85
86
  end
@@ -62,6 +62,7 @@ module I18n::Tasks::Data::Tree
62
62
  # TODO: support nested references better
63
63
  nodes do |node|
64
64
  next unless node.reference?
65
+
65
66
  old_target = [(node.root.key if root), node.value.to_s].compact.join('.')
66
67
  new_target = old_key_to_new_key[old_target]
67
68
  if new_target
@@ -92,6 +93,7 @@ module I18n::Tasks::Data::Tree
92
93
  # add or replace node by full key
93
94
  def set(full_key, node)
94
95
  fail 'value should be a I18n::Tasks::Data::Tree::Node' unless node.is_a?(Node)
96
+
95
97
  key_part, rest = split_key(full_key, 2)
96
98
  child = key_to_node[key_part]
97
99
 
@@ -131,6 +133,7 @@ module I18n::Tasks::Data::Tree
131
133
  def append!(nodes)
132
134
  nodes = nodes.map do |node|
133
135
  fail "already has a child with key '#{node.key}'" if key_to_node.key?(node.key)
136
+
134
137
  key_to_node[node.key] = (node.parent == parent ? node : node.derive(parent: parent))
135
138
  end
136
139
  super(nodes)
@@ -172,16 +175,18 @@ module I18n::Tasks::Data::Tree
172
175
 
173
176
  def set_root_key!(new_key, data = nil)
174
177
  return self if empty?
178
+
175
179
  rename_key first.key, new_key
176
180
  leaves { |node| node.data.merge! data } if data
177
181
  self
178
182
  end
179
183
 
180
184
  # @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,Metrics/PerceivedComplexity
185
+ def merge_node!(node, on_leaves_merge: nil) # rubocop:disable Metrics/AbcSize
182
186
  if key_to_node.key?(node.key)
183
187
  our = key_to_node[node.key]
184
188
  return if our == node
189
+
185
190
  our.value = node.value if node.leaf?
186
191
  our.data.merge!(node.data) if node.data?
187
192
  if node.children?
@@ -262,7 +267,7 @@ module I18n::Tasks::Data::Tree
262
267
  build_forest(warn_about_add_children_to_leaf: false) do |forest|
263
268
  key_occurrences.each do |key_occurrence|
264
269
  forest[key_occurrence.key] = ::I18n::Tasks::Data::Tree::Node.new(
265
- key: split_key(key_occurrence.key).last,
270
+ key: split_key(key_occurrence.key).last,
266
271
  data: { occurrences: key_occurrence.occurrences }
267
272
  )
268
273
  end
@@ -273,6 +278,7 @@ module I18n::Tasks::Data::Tree
273
278
  build_forest(opts) do |forest|
274
279
  key_attrs.each do |(full_key, attr)|
275
280
  fail "Invalid key #{full_key.inspect}" if full_key.end_with?('.')
281
+
276
282
  node = ::I18n::Tasks::Data::Tree::Node.new(**attr.merge(key: split_key(full_key).last))
277
283
  yield(full_key, node) if block
278
284
  forest[full_key] = node
@@ -295,6 +301,7 @@ module I18n::Tasks::Data::Tree
295
301
  def from_nested_hash(hash, opts = {})
296
302
  parse_parent_opt!(opts)
297
303
  fail I18n::Tasks::CommandError, "invalid tree #{hash.inspect}" unless hash.respond_to?(:map)
304
+
298
305
  opts[:nodes] = hash.map { |key, value| Node.from_key_value key, value }
299
306
  Siblings.new(opts)
300
307
  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] != b[0] ? a[0] <=> b[0] : a[1] <=> b[1] } if sort
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] != b[0] ? a[0] <=> b[0] : a[1] <=> b[1] } if sort
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,6 +170,7 @@ 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)
@@ -179,6 +187,7 @@ module I18n::Tasks
179
187
  pattern_re = I18n::Tasks::KeyPatternMatching.compile_key_pattern(key_pattern) if key_pattern.present?
180
188
  keys.each do |key, node|
181
189
  next if pattern_re && key !~ pattern_re
190
+
182
191
  node.value = value_proc.call(node)
183
192
  end
184
193
  self
@@ -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
10
  full_key =~ MAYBE_PLURAL_HTML_KEY_PATTERN &&
12
11
  depluralize_key(split_key(full_key, 2)[1], locale) =~ HTML_KEY_PATTERN)
13
- # rubocop:enable Style/DoubleNegation
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 = if type_ignore.is_a?(Array)
20
+ patterns = case type_ignore
21
+ when Array
21
22
  global + type_ignore
22
- elsif type_ignore.is_a?(Hash)
23
+ when Hash
23
24
  # ignore per locale
24
25
  global + (type_ignore['all'] || []) +
25
- type_ignore.select { |k, _v| k.to_s =~ /\b#{locale}\b/ }.values.flatten(1).compact
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,7 +2,7 @@
2
2
 
3
3
  module I18n::Tasks
4
4
  module Interpolations
5
- VARIABLE_REGEX = /%{[^}]+}/
5
+ VARIABLE_REGEX = /%{[^}]+}/.freeze
6
6
 
7
7
  def inconsistent_interpolations(locales: nil, base_locale: nil) # rubocop:disable Metrics/AbcSize
8
8
  locales ||= self.locales
@@ -11,10 +11,12 @@ module I18n::Tasks
11
11
 
12
12
  data[base_locale].key_values.each do |key, value|
13
13
  next if !value.is_a?(String) || ignore_key?(key, :inconsistent_interpolations)
14
+
14
15
  base_vars = Set.new(value.scan(VARIABLE_REGEX))
15
16
  (locales - [base_locale]).each do |current_locale|
16
17
  node = data[current_locale].first.children[key]
17
- next unless node&.value&.is_a?(String)
18
+ next unless node&.value.is_a?(String)
19
+
18
20
  if base_vars != Set.new(node.value.scan(VARIABLE_REGEX))
19
21
  result.merge!(node.walk_to_root.reduce(nil) { |c, p| [p.derive(children: c)] })
20
22
  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
- /(?:#{ key_patterns.map { |p| compile_key_pattern p } * '|' })/m
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
 
@@ -60,11 +60,14 @@ module I18n::Tasks
60
60
  locales.each_with_object(empty_forest) do |locale, forest|
61
61
  required_keys = required_plural_keys_for_locale(locale)
62
62
  next if required_keys.empty?
63
+
63
64
  tree = empty_forest
64
65
  plural_nodes data[locale] do |node|
65
66
  children = node.children
66
67
  present_keys = Set.new(children.map { |c| c.key.to_sym })
68
+ next if ignore_key?(node.full_key(root: false), :missing)
67
69
  next if present_keys.superset?(required_keys)
70
+
68
71
  tree[node.full_key] = node.derive(
69
72
  value: children.to_hash,
70
73
  children: nil,
@@ -79,12 +82,8 @@ module I18n::Tasks
79
82
  def required_plural_keys_for_locale(locale)
80
83
  @plural_keys_for_locale ||= {}
81
84
  return @plural_keys_for_locale[locale] if @plural_keys_for_locale.key?(locale)
82
- @plural_keys_for_locale[locale] =
83
- begin
84
- Set.new(load_rails_i18n_pluralization!(locale)[locale.to_sym][:i18n][:plural][:keys])
85
- rescue SystemCallError, IOError
86
- Set.new
87
- end
85
+
86
+ @plural_keys_for_locale[locale] = plural_keys_for_locale(locale)
88
87
  end
89
88
 
90
89
  # Loads rails-i18n pluralization config for the given locale.
@@ -123,7 +122,7 @@ module I18n::Tasks
123
122
  end
124
123
 
125
124
  def locale_key_missing?(locale, key)
126
- !key_value?(key, locale) && !external_key?(key) && !ignore_key?(key, :missing)
125
+ !key_value?(key, locale) && !external_key?(key, locale) && !ignore_key?(key, :missing)
127
126
  end
128
127
 
129
128
  # @param [::I18n::Tasks::Data::Tree::Siblings] forest
@@ -136,6 +135,7 @@ module I18n::Tasks
136
135
  locale = root.key
137
136
  root.keys do |key, node|
138
137
  next unless yield node
138
+
139
139
  if locales_and_node_by_key.key?(key)
140
140
  locales_and_node_by_key[key][0] << locale
141
141
  else
@@ -154,5 +154,32 @@ module I18n::Tasks
154
154
  end
155
155
  forest
156
156
  end
157
+
158
+ private
159
+
160
+ def plural_keys_for_locale(locale)
161
+ configuration = load_rails_i18n_pluralization!(locale)
162
+ if configuration[locale.to_sym].nil?
163
+ alternate_locale = alternate_locale_from(locale)
164
+ return Set.new if configuration[alternate_locale.to_sym].nil?
165
+
166
+ return set_from_rails_i18n_pluralization(configuration, alternate_locale)
167
+ end
168
+ set_from_rails_i18n_pluralization(configuration, locale)
169
+ rescue SystemCallError, IOError
170
+ Set.new
171
+ end
172
+
173
+ def alternate_locale_from(locale)
174
+ re = /(\w{2})-*(\w{2,3})*/
175
+ match = locale.match(re)
176
+ language_code = match[1]
177
+ country_code = match[2]
178
+ "#{language_code}-#{country_code.upcase}"
179
+ end
180
+
181
+ def set_from_rails_i18n_pluralization(configuration, locale)
182
+ Set.new(configuration[locale.to_sym][:i18n][:plural][:keys])
183
+ end
157
184
  end
158
185
  end
@@ -5,12 +5,13 @@ 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 * '|'})$/.freeze
9
9
 
10
10
  def collapse_plural_nodes!(tree)
11
11
  tree.leaves.map(&:parent).compact.uniq.each do |node|
12
12
  children = node.children
13
13
  next unless plural_forms?(children)
14
+
14
15
  node.value = children.to_hash
15
16
  node.children = nil
16
17
  node.data.merge! children.first.data
@@ -23,6 +24,7 @@ module I18n::Tasks::PluralKeys
23
24
  # @return [String] the base form if the key is a specific plural form (e.g. apple for apple.many), the key otherwise.
24
25
  def depluralize_key(key, locale = base_locale)
25
26
  return key if key !~ PLURAL_KEY_RE
27
+
26
28
  key_name = last_key_part(key)
27
29
  parent_key = key[0..- (key_name.length + 2)]
28
30
  nodes = tree("#{locale}.#{parent_key}").presence || (locale != base_locale && tree("#{base_locale}.#{parent_key}"))
@@ -37,10 +39,12 @@ module I18n::Tasks::PluralKeys
37
39
  # @yieldparam node [::I18n::Tasks::Data::Tree::Node] plural node
38
40
  def plural_nodes(tree)
39
41
  return to_enum(:plural_nodes, tree) unless block_given?
42
+
40
43
  visited = Set.new
41
44
  tree.leaves do |node|
42
45
  parent = node.parent
43
46
  next if !parent || visited.include?(parent)
47
+
44
48
  yield parent if plural_forms?(parent.children)
45
49
  visited.add(parent)
46
50
  end
@@ -49,6 +53,7 @@ module I18n::Tasks::PluralKeys
49
53
 
50
54
  def plural_forms?(s)
51
55
  return false if non_plural_other?(s)
56
+
52
57
  s.present? && s.all? { |node| node.leaf? && plural_suffix?(node.key) }
53
58
  end
54
59
 
@@ -10,12 +10,14 @@ module I18n::Tasks
10
10
  data_refs = merge_reference_trees(data_forest.select_keys { |_, node| node.reference? }))
11
11
  fail ArgumentError, 'usages must be a Data::Tree::Instance' unless usages.is_a?(Data::Tree::Siblings)
12
12
  fail ArgumentError, 'all_references must be a Data::Tree::Instance' unless data_refs.is_a?(Data::Tree::Siblings)
13
+
13
14
  raw_refs = empty_forest
14
15
  resolved_refs = empty_forest
15
16
  refs = empty_forest
16
17
  data_refs.key_to_node.each do |ref_key_part, ref_node|
17
18
  usages.each do |usage_node|
18
19
  next unless usage_node.key == ref_key_part
20
+
19
21
  if ref_node.leaf?
20
22
  process_leaf!(ref_node, usage_node, raw_refs, resolved_refs, refs)
21
23
  else
@@ -9,6 +9,7 @@ module I18n::Tasks::Reports
9
9
  end
10
10
 
11
11
  attr_reader :task
12
+
12
13
  delegate :base_locale, :locales, to: :task
13
14
 
14
15
  protected
@@ -58,6 +59,7 @@ module I18n::Tasks::Reports
58
59
 
59
60
  def format_locale(locale)
60
61
  return '' unless locale
62
+
61
63
  if locale.split('+') == task.locales.sort
62
64
  'all'
63
65
  else
@@ -67,8 +69,7 @@ module I18n::Tasks::Reports
67
69
 
68
70
  def collapse_missing_tree!(forest)
69
71
  forest = task.collapse_plural_nodes!(forest)
70
- forest = task.collapse_same_key_in_locales!(forest) { |node| node.data[:type] == :missing_used }
71
- forest
72
+ task.collapse_same_key_in_locales!(forest) { |node| node.data[:type] == :missing_used }
72
73
  end
73
74
  end
74
75
  end
@@ -106,9 +106,10 @@ module I18n
106
106
  private
107
107
 
108
108
  def missing_key_info(leaf)
109
- if leaf[:type] == :missing_used
109
+ case leaf[:type]
110
+ when :missing_used
110
111
  first_occurrence leaf
111
- elsif leaf[:type] == :missing_plural
112
+ when :missing_plural
112
113
  leaf[:data][:missing_keys].join(', ')
113
114
  else
114
115
  "#{Rainbow(leaf[:data][:missing_diff_locale]).cyan} "\
@@ -134,6 +135,7 @@ module I18n
134
135
 
135
136
  def format_reference_desc(node_data)
136
137
  return nil unless node_data
138
+
137
139
  case node_data[:ref_type]
138
140
  when :reference_usage
139
141
  Rainbow('(ref)').yellow.bright
@@ -159,9 +161,9 @@ module I18n
159
161
  print_table headings: [Rainbow(I18n.t('i18n_tasks.common.locale')).cyan.bright,
160
162
  Rainbow(I18n.t('i18n_tasks.common.key')).cyan.bright,
161
163
  I18n.t('i18n_tasks.common.value')] do |t|
162
- t.rows = locale_key_value_datas.map { |(locale, k, v, data)|
164
+ t.rows = locale_key_value_datas.map do |(locale, k, v, data)|
163
165
  [{ value: Rainbow(locale).cyan, alignment: :center }, format_key(k, data), format_value(v)]
164
- }
166
+ end
165
167
  end
166
168
  else
167
169
  puts 'ø'
@@ -11,10 +11,11 @@ module I18n::Tasks::Scanners
11
11
  attr_reader :config
12
12
 
13
13
  def initialize(
14
- config: {},
15
- file_finder_provider: Files::CachingFileFinderProvider.new,
16
- file_reader: Files::CachingFileReader.new
14
+ config: {},
15
+ file_finder_provider: Files::CachingFileFinderProvider.new,
16
+ file_reader: Files::CachingFileReader.new
17
17
  )
18
+ super()
18
19
  @config = config
19
20
  @file_reader = file_reader
20
21
  @file_finder = file_finder_provider.get(**config.slice(:paths, :only, :exclude))
@@ -22,9 +22,6 @@ module I18n::Tasks::Scanners::Files
22
22
  # @param (see FileFinder#traverse_files)
23
23
  # @yieldparam (see FileFinder#traverse_files)
24
24
  # @return (see FileFinder#traverse_files)
25
- def traverse_files
26
- super
27
- end
28
25
 
29
26
  alias uncached_find_files find_files
30
27
  private :uncached_find_files
@@ -15,6 +15,7 @@ module I18n::Tasks::Scanners::Files
15
15
  # Files matching any of the exclusion patterns will be excluded even if they match an inclusion pattern.
16
16
  def initialize(paths: ['.'], only: nil, exclude: [])
17
17
  fail 'paths argument is required' if paths.nil?
18
+
18
19
  @paths = paths
19
20
  @include = only
20
21
  @exclude = exclude || []
@@ -25,8 +26,8 @@ module I18n::Tasks::Scanners::Files
25
26
  # @yield [path]
26
27
  # @yieldparam path [String] the path of the found file.
27
28
  # @return [Array<of block results>]
28
- def traverse_files
29
- find_files.map { |path| yield path }
29
+ def traverse_files(&block)
30
+ find_files.map(&block)
30
31
  end
31
32
 
32
33
  # @return [Array<String>] found files
@@ -15,11 +15,11 @@ module I18n
15
15
  line_begin = contents.rindex(/^/, position - 1)
16
16
  line_end = contents.index(/.(?=\r?\n|$)/, position)
17
17
  Results::Occurrence.new(
18
- path: path,
19
- pos: position,
18
+ path: path,
19
+ pos: position,
20
20
  line_num: contents[0..position].count("\n") + 1,
21
21
  line_pos: position - line_begin + 1,
22
- line: contents[line_begin..line_end],
22
+ line: contents[line_begin..line_end],
23
23
  raw_key: raw_key
24
24
  )
25
25
  end
@@ -12,7 +12,7 @@ module I18n::Tasks::Scanners
12
12
  include OccurrenceFromPosition
13
13
  include RubyKeyLiterals
14
14
 
15
- TRANSLATE_CALL_RE = /(?<=^|[^\w'\-.]|[^\w'\-]I18n\.|I18n\.)t(?:!|ranslate!?)?/
15
+ TRANSLATE_CALL_RE = /(?<=^|[^\w'\-.]|[^\w'\-]I18n\.|I18n\.)t(?:!|ranslate!?)?/.freeze
16
16
  IGNORE_LINES = {
17
17
  'coffee' => /^\s*#(?!\si18n-tasks-use)/,
18
18
  'erb' => /^\s*<%\s*#(?!\si18n-tasks-use)/,
@@ -43,10 +43,13 @@ module I18n::Tasks::Scanners
43
43
  src_pos = Regexp.last_match.offset(0).first
44
44
  location = occurrence_from_position(path, text, src_pos, raw_key: strip_literal(match[0]))
45
45
  next if exclude_line?(location.line, path)
46
+
46
47
  key = match_to_key(match, path, location)
47
48
  next unless key
49
+
48
50
  key += ':' if key.end_with?('.')
49
51
  next unless valid_key?(key)
52
+
50
53
  keys << [key, location]
51
54
  end
52
55
  keys
@@ -67,7 +70,7 @@ module I18n::Tasks::Scanners
67
70
  re && re =~ line
68
71
  end
69
72
 
70
- VALID_KEY_RE_DYNAMIC = /^(#{VALID_KEY_CHARS}|[:\#{@}\[\]])+$/
73
+ VALID_KEY_RE_DYNAMIC = /^(#{VALID_KEY_CHARS}|[:\#{@}\[\]])+$/.freeze
71
74
 
72
75
  def valid_key?(key)
73
76
  if @config[:strict]
@@ -84,7 +87,7 @@ module I18n::Tasks::Scanners
84
87
  def closest_method(occurrence)
85
88
  method = File.readlines(occurrence.path, encoding: 'UTF-8')
86
89
  .first(occurrence.line_num - 1).reverse_each.find { |x| x =~ /\bdef\b/ }
87
- method && method.strip.sub(/^def\s*/, '').sub(/[\(\s;].*$/, '')
90
+ method && method.strip.sub(/^def\s*/, '').sub(/[(\s;].*$/, '')
88
91
  end
89
92
 
90
93
  # This method only exists for backwards compatibility with monkey-patches and plugins
@@ -93,7 +96,7 @@ module I18n::Tasks::Scanners
93
96
  def default_pattern
94
97
  # capture only the first argument
95
98
  /
96
- #{translate_call_re} [\( ] \s* (?# fn call begin )
99
+ #{translate_call_re} [( ] \s* (?# fn call begin )
97
100
  (#{first_argument_re}) (?# capture the first argument)
98
101
  /x
99
102
  end