i18n-tasks 0.9.6 → 0.9.7

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.
Files changed (70) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +1 -1
  3. data/Rakefile +2 -1
  4. data/bin/i18n-tasks +1 -0
  5. data/config/locales/en.yml +15 -9
  6. data/config/locales/ru.yml +80 -74
  7. data/i18n-tasks.gemspec +9 -8
  8. data/lib/i18n/tasks.rb +2 -5
  9. data/lib/i18n/tasks/cli.rb +14 -18
  10. data/lib/i18n/tasks/command/commander.rb +2 -3
  11. data/lib/i18n/tasks/command/commands/health.rb +1 -1
  12. data/lib/i18n/tasks/command/commands/meta.rb +2 -2
  13. data/lib/i18n/tasks/command/commands/missing.rb +17 -19
  14. data/lib/i18n/tasks/command/commands/tree.rb +3 -3
  15. data/lib/i18n/tasks/command/commands/usages.rb +6 -5
  16. data/lib/i18n/tasks/command/commands/xlsx.rb +1 -1
  17. data/lib/i18n/tasks/command/dsl.rb +1 -2
  18. data/lib/i18n/tasks/command/option_parsers/enum.rb +6 -6
  19. data/lib/i18n/tasks/command/option_parsers/locale.rb +3 -1
  20. data/lib/i18n/tasks/command/options/data.rb +22 -21
  21. data/lib/i18n/tasks/command_error.rb +1 -3
  22. data/lib/i18n/tasks/configuration.rb +37 -27
  23. data/lib/i18n/tasks/console_context.rb +3 -2
  24. data/lib/i18n/tasks/data.rb +5 -4
  25. data/lib/i18n/tasks/data/adapter/json_adapter.rb +13 -12
  26. data/lib/i18n/tasks/data/adapter/yaml_adapter.rb +13 -14
  27. data/lib/i18n/tasks/data/file_formats.rb +15 -10
  28. data/lib/i18n/tasks/data/file_system_base.rb +18 -22
  29. data/lib/i18n/tasks/data/router/conservative_router.rb +3 -6
  30. data/lib/i18n/tasks/data/router/pattern_router.rb +4 -5
  31. data/lib/i18n/tasks/data/tree/node.rb +20 -19
  32. data/lib/i18n/tasks/data/tree/nodes.rb +4 -3
  33. data/lib/i18n/tasks/data/tree/siblings.rb +36 -29
  34. data/lib/i18n/tasks/data/tree/traversal.rb +32 -35
  35. data/lib/i18n/tasks/google_translation.rb +22 -25
  36. data/lib/i18n/tasks/html_keys.rb +2 -0
  37. data/lib/i18n/tasks/ignore_keys.rb +3 -2
  38. data/lib/i18n/tasks/key_pattern_matching.rb +9 -8
  39. data/lib/i18n/tasks/locale_list.rb +4 -6
  40. data/lib/i18n/tasks/locale_pathname.rb +8 -8
  41. data/lib/i18n/tasks/logging.rb +3 -3
  42. data/lib/i18n/tasks/missing_keys.rb +29 -31
  43. data/lib/i18n/tasks/plural_keys.rb +6 -7
  44. data/lib/i18n/tasks/references.rb +72 -41
  45. data/lib/i18n/tasks/reports/base.rb +8 -7
  46. data/lib/i18n/tasks/reports/spreadsheet.rb +15 -16
  47. data/lib/i18n/tasks/reports/terminal.rb +32 -32
  48. data/lib/i18n/tasks/scanners/file_scanner.rb +6 -5
  49. data/lib/i18n/tasks/scanners/files/caching_file_finder_provider.rb +1 -2
  50. data/lib/i18n/tasks/scanners/files/caching_file_reader.rb +0 -1
  51. data/lib/i18n/tasks/scanners/files/file_finder.rb +12 -15
  52. data/lib/i18n/tasks/scanners/files/file_reader.rb +0 -1
  53. data/lib/i18n/tasks/scanners/occurrence_from_position.rb +8 -7
  54. data/lib/i18n/tasks/scanners/pattern_mapper.rb +2 -2
  55. data/lib/i18n/tasks/scanners/pattern_scanner.rb +9 -7
  56. data/lib/i18n/tasks/scanners/pattern_with_scope_scanner.rb +1 -2
  57. data/lib/i18n/tasks/scanners/relative_keys.rb +11 -11
  58. data/lib/i18n/tasks/scanners/results/key_occurrences.rb +3 -4
  59. data/lib/i18n/tasks/scanners/results/occurrence.rb +8 -7
  60. data/lib/i18n/tasks/scanners/ruby_ast_call_finder.rb +2 -2
  61. data/lib/i18n/tasks/scanners/ruby_ast_scanner.rb +39 -31
  62. data/lib/i18n/tasks/scanners/scanner_multiplexer.rb +9 -3
  63. data/lib/i18n/tasks/split_key.rb +5 -6
  64. data/lib/i18n/tasks/stats.rb +10 -8
  65. data/lib/i18n/tasks/string_interpolation.rb +1 -1
  66. data/lib/i18n/tasks/unused_keys.rb +2 -2
  67. data/lib/i18n/tasks/used_keys.rb +47 -43
  68. data/lib/i18n/tasks/version.rb +1 -1
  69. data/templates/rspec/i18n_spec.rb +2 -2
  70. metadata +30 -3
@@ -25,9 +25,15 @@ module I18n::Tasks::Scanners
25
25
  def collect_results
26
26
  return [@scanners[0].keys] if @scanners.length == 1
27
27
  Array.new(@scanners.length).tap do |results|
28
- @scanners.map.with_index { |scanner, i|
29
- Thread.start { results[i] = scanner.keys }
30
- }.each(&:join)
28
+ results_mutex = Mutex.new
29
+ @scanners.map.with_index do |scanner, i|
30
+ Thread.start do
31
+ scanner_results = scanner.keys
32
+ results_mutex.synchronize do
33
+ results[i] = scanner_results
34
+ end
35
+ end
36
+ end.each(&:join)
31
37
  end
32
38
  end
33
39
  end
@@ -2,7 +2,7 @@
2
2
  module I18n
3
3
  module Tasks
4
4
  module SplitKey
5
- extend self
5
+ module_function
6
6
 
7
7
  # split a key by dots (.)
8
8
  # dots inside braces or parenthesis are not split on
@@ -37,7 +37,7 @@ module I18n
37
37
  return enum_for(:key_parts, key) unless block
38
38
  nesting = PARENS
39
39
  counts = PARENS_ZEROS # dup'd later if key contains parenthesis
40
- delim = '.'.freeze
40
+ delim = '.'
41
41
  from = to = 0
42
42
  key.each_char do |char|
43
43
  if char == delim && PARENS_ZEROS == counts
@@ -46,7 +46,7 @@ module I18n
46
46
  else
47
47
  nest_i, nest_inc = nesting[char]
48
48
  if nest_i
49
- counts = counts.dup if counts.frozen?
49
+ counts = counts.dup if counts.frozen?
50
50
  counts[nest_i] += nest_inc
51
51
  end
52
52
  to += 1
@@ -56,12 +56,11 @@ module I18n
56
56
  true
57
57
  end
58
58
 
59
- PARENS = %w({} [] ()).inject({}) { |h, s|
59
+ PARENS = %w({} [] ()).each_with_object({}) do |s, h|
60
60
  i = h.size / 2
61
61
  h[s[0].freeze] = [i, 1].freeze
62
62
  h[s[1].freeze] = [i, -1].freeze
63
- h
64
- }.freeze
63
+ end.freeze
65
64
  PARENS_ZEROS = Array.new(PARENS.size, 0).freeze
66
65
  private_constant :PARENS
67
66
  private_constant :PARENS_ZEROS
@@ -2,18 +2,20 @@
2
2
  module I18n::Tasks
3
3
  module Stats
4
4
  def forest_stats(forest)
5
- key_count = forest.leaves.count
5
+ key_count = forest.leaves.count
6
6
  locale_count = forest.count
7
7
  if key_count.zero?
8
- {key_count: 0}
8
+ { key_count: 0 }
9
9
  else
10
10
  {
11
- locales: forest.map(&:key).join(', '),
12
- key_count: key_count,
13
- locale_count: locale_count,
14
- per_locale_avg: forest.inject(0) { |sum, f| sum + f.leaves.count } / locale_count,
15
- key_segments_avg: '%.1f' % (forest.leaves.inject(0) { |sum, node| sum + node.walk_to_root.count - 1 } / key_count.to_f),
16
- value_chars_avg: forest.leaves.inject(0) { |sum, node| sum + node.value.to_s.length } / key_count
11
+ locales: forest.map(&:key).join(', '),
12
+ key_count: key_count,
13
+ locale_count: locale_count,
14
+ per_locale_avg: forest.inject(0) { |sum, f| sum + f.leaves.count } / locale_count,
15
+ key_segments_avg: format(
16
+ '%.1f', forest.leaves.inject(0) { |sum, node| sum + node.walk_to_root.count - 1 } / key_count.to_f
17
+ ),
18
+ value_chars_avg: forest.leaves.inject(0) { |sum, node| sum + node.value.to_s.length } / key_count
17
19
  }
18
20
  end
19
21
  end
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
  module I18n::Tasks
3
3
  module StringInterpolation
4
- extend self
4
+ module_function
5
5
 
6
6
  def interpolate_soft(s, t = {})
7
7
  return s unless s
@@ -15,8 +15,8 @@ module I18n
15
15
  used_key_names = used_tree(strict: true).key_names
16
16
  collapse_plural_nodes! data[locale].select_keys { |key, _node|
17
17
  !ignore_key?(key, :unused) &&
18
- (strict || !used_in_expr?(key)) &&
19
- !used_key_names.include?(depluralize_key(key, locale))
18
+ (strict || !used_in_expr?(key)) &&
19
+ !used_key_names.include?(depluralize_key(key, locale))
20
20
  }
21
21
  end
22
22
  end
@@ -6,25 +6,30 @@ require 'i18n/tasks/scanners/scanner_multiplexer'
6
6
  require 'i18n/tasks/scanners/files/caching_file_finder_provider'
7
7
  require 'i18n/tasks/scanners/files/caching_file_reader'
8
8
 
9
+ # Require the pattern mapper even though it's not used by i18n-tasks directly.
10
+ # This allows the user to use it in config/i18n-tasks.yml without having to require it.
11
+ # See https://github.com/glebm/i18n-tasks/issues/204.
12
+ require 'i18n/tasks/scanners/pattern_mapper'
13
+
9
14
  module I18n::Tasks
10
- module UsedKeys
15
+ module UsedKeys # rubocop:disable Metrics/ModuleLength
11
16
  SEARCH_DEFAULTS = {
12
- paths: %w(app/).freeze,
13
- relative_roots: %w(app/controllers app/helpers app/mailers app/presenters app/views).freeze,
14
- scanners: [['::I18n::Tasks::Scanners::RubyAstScanner', only: %w(*.rb)]],
15
- strict: true,
16
- }.tap { |defaults|
17
+ paths: %w(app/).freeze,
18
+ relative_roots: %w(app/controllers app/helpers app/mailers app/presenters app/views).freeze,
19
+ scanners: [['::I18n::Tasks::Scanners::RubyAstScanner', only: %w(*.rb)]],
20
+ strict: true
21
+ }.tap do |defaults|
17
22
  defaults[:scanners] << ['::I18n::Tasks::Scanners::PatternWithScopeScanner',
18
23
  exclude: defaults[:scanners].map { |(_, opts)| opts[:only] }.reduce(:+).freeze,
19
- ignore_lines: {'opal' => %q(^\s*#(?!\si18n-tasks-use)),
20
- 'haml' => %q(^\s*-\s*#(?!\si18n-tasks-use)),
21
- 'slim' => %q(^\s*(?:-#|/)(?!\si18n-tasks-use)),
22
- 'coffee' => %q(^\s*#(?!\si18n-tasks-use)),
23
- 'erb' => %q(^\s*<%\s*#(?!\si18n-tasks-use))}.freeze] }
24
-
24
+ ignore_lines: { 'opal' => %q(^\s*#(?!\si18n-tasks-use)),
25
+ 'haml' => %q(^\s*-\s*#(?!\si18n-tasks-use)),
26
+ 'slim' => %q(^\s*(?:-#|/)(?!\si18n-tasks-use)),
27
+ 'coffee' => %q(^\s*#(?!\si18n-tasks-use)),
28
+ 'erb' => %q(^\s*<%\s*#(?!\si18n-tasks-use)) }.freeze]
29
+ end
25
30
 
26
31
  ALWAYS_EXCLUDE = %w(*.jpg *.png *.gif *.svg *.ico *.eot *.otf *.ttf *.woff *.woff2 *.pdf *.css *.sass *.scss *.less
27
- *.yml *.json *.zip *.tar.gz)
32
+ *.yml *.json *.zip *.tar.gz).freeze
28
33
 
29
34
  # Find all keys in the source and return a forest with the keys in absolute form and their occurrences.
30
35
  #
@@ -41,28 +46,25 @@ module I18n::Tasks
41
46
  src_tree.tap do |result|
42
47
  tree = result['used'].children
43
48
  tree.subtract_by_key!(raw_refs)
44
- if include_raw_references
45
- tree.merge!(raw_refs)
46
- end
49
+ tree.merge!(raw_refs) if include_raw_references
47
50
  tree.merge!(used_refs).merge!(resolved_refs)
48
51
  end
49
52
  end
50
53
 
51
54
  def used_in_source_tree(key_filter: nil, strict: nil)
52
55
  keys = ((@keys_used_in_source_tree ||= {})[strict?(strict)] ||=
53
- scanner(strict: strict).keys.freeze)
56
+ scanner(strict: strict).keys.freeze)
54
57
  if key_filter
55
58
  key_filter_re = compile_key_pattern(key_filter)
56
59
  keys = keys.reject { |k| k.key !~ key_filter_re }
57
60
  end
58
61
  Data::Tree::Node.new(
59
- key: 'used',
60
- data: {key_filter: key_filter},
61
- children: Data::Tree::Siblings.from_key_occurrences(keys)
62
+ key: 'used',
63
+ data: { key_filter: key_filter },
64
+ children: Data::Tree::Siblings.from_key_occurrences(keys)
62
65
  ).to_siblings
63
66
  end
64
67
 
65
-
66
68
  def scanner(strict: nil)
67
69
  (@scanner ||= {})[strict?(strict)] ||= begin
68
70
  shared_options = search_config.dup
@@ -70,16 +72,18 @@ module I18n::Tasks
70
72
  shared_options[:strict] = strict unless strict.nil?
71
73
  log_verbose 'Scanners: '
72
74
  Scanners::ScannerMultiplexer.new(
73
- scanners: search_config[:scanners].map { |(class_name, args)|
74
- if args && args[:strict]
75
- fail CommandError.new('the strict option is global and cannot be applied on the scanner level')
76
- end
77
-
78
- ActiveSupport::Inflector.constantize(class_name).new(
79
- config: merge_scanner_configs(shared_options, args || {}),
80
- file_finder_provider: caching_file_finder_provider,
81
- file_reader: caching_file_reader)
82
- }.tap { |scanners| log_verbose { scanners.map { |s| " #{s.class.name} #{s.config.inspect}" } * "\n" } })
75
+ scanners: search_config[:scanners].map do |(class_name, args)|
76
+ if args && args[:strict]
77
+ fail CommandError, 'the strict option is global and cannot be applied on the scanner level'
78
+ end
79
+
80
+ ActiveSupport::Inflector.constantize(class_name).new(
81
+ config: merge_scanner_configs(shared_options, args || {}),
82
+ file_finder_provider: caching_file_finder_provider,
83
+ file_reader: caching_file_reader
84
+ )
85
+ end.tap { |scanners| log_verbose { scanners.map { |s| " #{s.class.name} #{s.config.inspect}" } * "\n" } }
86
+ )
83
87
  end
84
88
  end
85
89
 
@@ -122,9 +126,9 @@ module I18n::Tasks
122
126
  @caching_file_reader ||= Scanners::Files::CachingFileReader.new
123
127
  end
124
128
 
125
- # @return whether the key is potentially used in a code expression such as `t("category.#{ category_key }")`
129
+ # @return [Boolean] whether the key is potentially used in a code expression such as `t("category.#{category_key}")`
126
130
  def used_in_expr?(key)
127
- !!(key =~ expr_key_re)
131
+ !!(key =~ expr_key_re) # rubocop:disable Style/DoubleNegation
128
132
  end
129
133
 
130
134
  private
@@ -137,17 +141,17 @@ module I18n::Tasks
137
141
 
138
142
  # keys in the source that end with a ., e.g. t("category.#{ cat.i18n_key }") or t("category." + category.key)
139
143
  # @param [String] replacement for interpolated values.
140
- def expr_key_re(replacement: ':'.freeze)
144
+ def expr_key_re(replacement: ':')
141
145
  @expr_key_re ||= begin
142
146
  # disallow patterns with no keys
143
147
  ignore_pattern_re = /\A[\.#{replacement}]*\z/
144
- patterns = used_in_source_tree(strict: false).key_names.select { |k|
145
- k.end_with?('.'.freeze) || k =~ /\#{/.freeze
146
- }.map { |k|
147
- pattern = "#{replace_key_exp(k, replacement)}#{replacement if k.end_with?('.'.freeze)}"
148
+ patterns = used_in_source_tree(strict: false).key_names.select do |k|
149
+ k.end_with?('.') || k =~ /\#{/
150
+ end.map do |k|
151
+ pattern = "#{replace_key_exp(k, replacement)}#{replacement if k.end_with?('.')}"
148
152
  next if pattern =~ ignore_pattern_re
149
153
  pattern
150
- }.compact
154
+ end.compact
151
155
  compile_key_pattern "{#{patterns * ','}}"
152
156
  end
153
157
  end
@@ -160,15 +164,15 @@ module I18n::Tasks
160
164
  scanner = StringScanner.new(key)
161
165
  braces = []
162
166
  result = []
163
- while (match_until = scanner.scan_until(/(?:#?\{|})/.freeze))
164
- if scanner.matched == '#{'.freeze
167
+ while (match_until = scanner.scan_until(/(?:#?\{|})/))
168
+ if scanner.matched == '#{'
165
169
  braces << scanner.matched
166
170
  result << match_until[0..-3] if braces.length == 1
167
171
  elsif scanner.matched == '}'
168
172
  prev_brace = braces.pop
169
- result << replacement if braces.empty? && prev_brace == '#{'.freeze
173
+ result << replacement if braces.empty? && prev_brace == '#{'
170
174
  else
171
- braces << '{'.freeze
175
+ braces << '{'
172
176
  end
173
177
  end
174
178
  result << key[scanner.pos..-1] unless scanner.eos?
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
  module I18n
3
3
  module Tasks
4
- VERSION = '0.9.6'
4
+ VERSION = '0.9.7'
5
5
  end
6
6
  end
@@ -8,11 +8,11 @@ RSpec.describe 'I18n' do
8
8
 
9
9
  it 'does not have missing keys' do
10
10
  expect(missing_keys).to be_empty,
11
- "Missing #{missing_keys.leaves.count} i18n keys, run `i18n-tasks missing' to show them"
11
+ "Missing #{missing_keys.leaves.count} i18n keys, run `i18n-tasks missing' to show them"
12
12
  end
13
13
 
14
14
  it 'does not have unused keys' do
15
15
  expect(unused_keys).to be_empty,
16
- "#{unused_keys.leaves.count} unused i18n keys, run `i18n-tasks unused' to show them"
16
+ "#{unused_keys.leaves.count} unused i18n keys, run `i18n-tasks unused' to show them"
17
17
  end
18
18
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: i18n-tasks
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.9.6
4
+ version: 0.9.7
5
5
  platform: ruby
6
6
  authors:
7
7
  - glebm
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2016-11-01 00:00:00.000000000 Z
11
+ date: 2016-12-27 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -192,6 +192,34 @@ dependencies:
192
192
  - - "~>"
193
193
  - !ruby/object:Gem::Version
194
194
  version: '3.3'
195
+ - !ruby/object:Gem::Dependency
196
+ name: rubocop
197
+ requirement: !ruby/object:Gem::Requirement
198
+ requirements:
199
+ - - ">="
200
+ - !ruby/object:Gem::Version
201
+ version: '0'
202
+ type: :development
203
+ prerelease: false
204
+ version_requirements: !ruby/object:Gem::Requirement
205
+ requirements:
206
+ - - ">="
207
+ - !ruby/object:Gem::Version
208
+ version: '0'
209
+ - !ruby/object:Gem::Dependency
210
+ name: simplecov
211
+ requirement: !ruby/object:Gem::Requirement
212
+ requirements:
213
+ - - ">="
214
+ - !ruby/object:Gem::Version
215
+ version: '0'
216
+ type: :development
217
+ prerelease: false
218
+ version_requirements: !ruby/object:Gem::Requirement
219
+ requirements:
220
+ - - ">="
221
+ - !ruby/object:Gem::Version
222
+ version: '0'
195
223
  - !ruby/object:Gem::Dependency
196
224
  name: yard
197
225
  requirement: !ruby/object:Gem::Requirement
@@ -330,4 +358,3 @@ signing_key:
330
358
  specification_version: 4
331
359
  summary: Manage localization and translation with the awesome power of static analysis
332
360
  test_files: []
333
- has_rdoc: