i18n-tasks 0.9.33 → 0.9.34

Sign up to get free protection for your applications and to get access to all the features.
Files changed (57) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +2 -2
  3. data/i18n-tasks.gemspec +4 -4
  4. data/lib/i18n/tasks/cli.rb +12 -14
  5. data/lib/i18n/tasks/command/commands/data.rb +8 -6
  6. data/lib/i18n/tasks/command/commands/eq_base.rb +2 -2
  7. data/lib/i18n/tasks/command/commands/health.rb +3 -2
  8. data/lib/i18n/tasks/command/commands/interpolations.rb +1 -1
  9. data/lib/i18n/tasks/command/commands/meta.rb +1 -1
  10. data/lib/i18n/tasks/command/commands/missing.rb +7 -6
  11. data/lib/i18n/tasks/command/commands/tree.rb +8 -6
  12. data/lib/i18n/tasks/command/commands/usages.rb +5 -4
  13. data/lib/i18n/tasks/command/dsl.rb +4 -4
  14. data/lib/i18n/tasks/command/option_parsers/enum.rb +2 -0
  15. data/lib/i18n/tasks/command/option_parsers/locale.rb +2 -1
  16. data/lib/i18n/tasks/command/options/data.rb +1 -0
  17. data/lib/i18n/tasks/command/options/locales.rb +5 -5
  18. data/lib/i18n/tasks/concurrent/cached_value.rb +2 -0
  19. data/lib/i18n/tasks/configuration.rb +4 -4
  20. data/lib/i18n/tasks/console_context.rb +1 -1
  21. data/lib/i18n/tasks/data/file_formats.rb +2 -0
  22. data/lib/i18n/tasks/data/file_system_base.rb +7 -6
  23. data/lib/i18n/tasks/data/router/conservative_router.rb +2 -1
  24. data/lib/i18n/tasks/data/router/pattern_router.rb +2 -0
  25. data/lib/i18n/tasks/data/tree/node.rb +5 -2
  26. data/lib/i18n/tasks/data/tree/nodes.rb +1 -0
  27. data/lib/i18n/tasks/data/tree/siblings.rb +9 -2
  28. data/lib/i18n/tasks/data/tree/traversal.rb +11 -2
  29. data/lib/i18n/tasks/html_keys.rb +2 -4
  30. data/lib/i18n/tasks/ignore_keys.rb +4 -3
  31. data/lib/i18n/tasks/interpolations.rb +4 -2
  32. data/lib/i18n/tasks/key_pattern_matching.rb +3 -2
  33. data/lib/i18n/tasks/missing_keys.rb +3 -0
  34. data/lib/i18n/tasks/plural_keys.rb +6 -1
  35. data/lib/i18n/tasks/references.rb +2 -0
  36. data/lib/i18n/tasks/reports/base.rb +3 -2
  37. data/lib/i18n/tasks/reports/terminal.rb +6 -4
  38. data/lib/i18n/tasks/scanners/file_scanner.rb +4 -3
  39. data/lib/i18n/tasks/scanners/files/caching_file_finder.rb +0 -3
  40. data/lib/i18n/tasks/scanners/files/file_finder.rb +3 -2
  41. data/lib/i18n/tasks/scanners/occurrence_from_position.rb +3 -3
  42. data/lib/i18n/tasks/scanners/pattern_scanner.rb +7 -4
  43. data/lib/i18n/tasks/scanners/pattern_with_scope_scanner.rb +4 -2
  44. data/lib/i18n/tasks/scanners/relative_keys.rb +1 -0
  45. data/lib/i18n/tasks/scanners/ruby_ast_scanner.rb +16 -13
  46. data/lib/i18n/tasks/scanners/ruby_key_literals.rb +3 -3
  47. data/lib/i18n/tasks/scanners/scanner_multiplexer.rb +2 -0
  48. data/lib/i18n/tasks/split_key.rb +2 -0
  49. data/lib/i18n/tasks/string_interpolation.rb +1 -0
  50. data/lib/i18n/tasks/translation.rb +3 -3
  51. data/lib/i18n/tasks/translators/base_translator.rb +3 -1
  52. data/lib/i18n/tasks/translators/deepl_translator.rb +3 -1
  53. data/lib/i18n/tasks/translators/google_translator.rb +2 -0
  54. data/lib/i18n/tasks/translators/yandex_translator.rb +2 -0
  55. data/lib/i18n/tasks/used_keys.rb +13 -10
  56. data/lib/i18n/tasks/version.rb +1 -1
  57. metadata +5 -5
@@ -19,8 +19,10 @@ module I18n::Tasks::Concurrent
19
19
  # @return [Object] Result of the computation.
20
20
  def get
21
21
  return get_result_volatile unless get_result_volatile == NULL
22
+
22
23
  @mutex.synchronize do
23
24
  next unless get_result_volatile == NULL
25
+
24
26
  set_result_volatile @computation.call
25
27
  @computation = nil
26
28
  end
@@ -2,10 +2,10 @@
2
2
 
3
3
  module I18n::Tasks::Configuration # rubocop:disable Metrics/ModuleLength
4
4
  DEFAULTS = {
5
- base_locale: 'en',
5
+ base_locale: 'en',
6
6
  internal_locale: 'en',
7
- search: ::I18n::Tasks::UsedKeys::SEARCH_DEFAULTS,
8
- data: ::I18n::Tasks::Data::DATA_DEFAULTS
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)
@@ -47,7 +47,7 @@ module I18n::Tasks::Configuration # rubocop:disable Metrics/ModuleLength
47
47
  @config_sections[:data] ||= begin
48
48
  {
49
49
  adapter: data.class.name,
50
- config: data.config
50
+ config: data.config
51
51
  }
52
52
  end
53
53
  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 + "\n" + <<~TEXT
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)
@@ -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
58
  ::File.open(path, 'w') { |f| f.write 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
- 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,13 @@ 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 })
67
68
  next if present_keys.superset?(required_keys)
69
+
68
70
  tree[node.full_key] = node.derive(
69
71
  value: children.to_hash,
70
72
  children: nil,
@@ -132,6 +134,7 @@ module I18n::Tasks
132
134
  locale = root.key
133
135
  root.keys do |key, node|
134
136
  next unless yield node
137
+
135
138
  if locales_and_node_by_key.key?(key)
136
139
  locales_and_node_by_key[key][0] << locale
137
140
  else
@@ -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