i18n-tasks 0.9.6 → 0.9.7

Sign up to get free protection for your applications and to get access to all the features.
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: