i18n-tasks 1.0.8 → 1.0.11

Sign up to get free protection for your applications and to get access to all the features.
Files changed (34) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +17 -4
  3. data/i18n-tasks.gemspec +9 -4
  4. data/lib/i18n/tasks/cli.rb +5 -4
  5. data/lib/i18n/tasks/command/options/common.rb +0 -1
  6. data/lib/i18n/tasks/command/options/data.rb +1 -1
  7. data/lib/i18n/tasks/concurrent/cached_value.rb +0 -2
  8. data/lib/i18n/tasks/configuration.rb +11 -6
  9. data/lib/i18n/tasks/data/adapter/yaml_adapter.rb +2 -2
  10. data/lib/i18n/tasks/data/file_formats.rb +1 -1
  11. data/lib/i18n/tasks/data/router/pattern_router.rb +1 -1
  12. data/lib/i18n/tasks/data/tree/node.rb +1 -1
  13. data/lib/i18n/tasks/data/tree/nodes.rb +5 -7
  14. data/lib/i18n/tasks/data/tree/siblings.rb +1 -2
  15. data/lib/i18n/tasks/html_keys.rb +2 -2
  16. data/lib/i18n/tasks/references.rb +2 -2
  17. data/lib/i18n/tasks/reports/base.rb +1 -1
  18. data/lib/i18n/tasks/reports/terminal.rb +2 -2
  19. data/lib/i18n/tasks/scanners/ast_matchers/base_matcher.rb +5 -1
  20. data/lib/i18n/tasks/scanners/ast_matchers/rails_model_matcher.rb +69 -0
  21. data/lib/i18n/tasks/scanners/pattern_mapper.rb +1 -1
  22. data/lib/i18n/tasks/scanners/pattern_scanner.rb +1 -1
  23. data/lib/i18n/tasks/scanners/relative_keys.rb +2 -2
  24. data/lib/i18n/tasks/scanners/results/occurrence.rb +1 -1
  25. data/lib/i18n/tasks/scanners/ruby_ast_scanner.rb +8 -3
  26. data/lib/i18n/tasks/scanners/ruby_key_literals.rb +2 -2
  27. data/lib/i18n/tasks/split_key.rb +1 -1
  28. data/lib/i18n/tasks/translators/base_translator.rb +1 -1
  29. data/lib/i18n/tasks/translators/deepl_translator.rb +2 -2
  30. data/lib/i18n/tasks/used_keys.rb +3 -2
  31. data/lib/i18n/tasks/version.rb +1 -1
  32. data/lib/i18n/tasks.rb +11 -0
  33. data/templates/config/i18n-tasks.yml +12 -1
  34. metadata +18 -14
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 9d1d2fc604c929450bdf96a547513004630f57a1da7e368020b0313f4e4c67f4
4
- data.tar.gz: 4b1a226b25f08c76d92998bc8cecd37de5e698f2f805da606804b8faa3fc0203
3
+ metadata.gz: 76a7092606f1ea327f57a9a2bc0489c07d5349f61b6b742415d0dd367b243237
4
+ data.tar.gz: dd87c3098d774df0fe685829cefe3716e9cd42156cb483762a491174d0eaf733
5
5
  SHA512:
6
- metadata.gz: aeff02b2b63a8d64a37e80342b4dcf41c1a2c0379a8792b1064c6af431e18350c3130b890ee8bb83b4cc0cc6527b0721dabd2c7eb96ea420414b749d7c1b7a35
7
- data.tar.gz: ce61d0886ca762588e101032f2d51207742f24f30d5c8d681594ea44ae08f508ac5f032f6bad2e27b117bf349794089753cd9669aa9e25de5d72831ac5db2d63
6
+ metadata.gz: 7341b68328712adcbbb7b28b9c83695a56fcaac29eb9d7303c8866cf0f6004bd7e85065a1684a3acb1597786c1993b876e8eab04f5db555d95563f43976e7934
7
+ data.tar.gz: 8b8f93c545f0cc80b3ee36610751db469c1cf6c47df4ba9376189c926480dc0a96d678545c90163f2f24f1826beeaaff1e24ec9bc273a24541e715b0701ceb61
data/README.md CHANGED
@@ -24,7 +24,7 @@ i18n-tasks can be used with any project using the ruby [i18n gem][i18n-gem] (def
24
24
  Add i18n-tasks to the Gemfile:
25
25
 
26
26
  ```ruby
27
- gem 'i18n-tasks', '~> 1.0.8'
27
+ gem 'i18n-tasks', '~> 1.0.11'
28
28
  ```
29
29
 
30
30
  Copy the default [configuration file](#configuration):
@@ -355,7 +355,7 @@ If you have implemented a custom adapter please share it on [the wiki][wiki].
355
355
 
356
356
  ### Usage search
357
357
 
358
- i18n-tasks uses an AST scanner for `.rb` files, and a regexp scanner for all other files.
358
+ i18n-tasks uses an AST scanner for `.rb` and `.html.erb` files, and a regexp scanner for all other files.
359
359
  New scanners can be added easily: please refer to [this example](https://github.com/glebm/i18n-tasks/wiki/A-custom-scanner-example).
360
360
 
361
361
  See the `search` section in the [config file][config] for all available configuration options.
@@ -436,15 +436,28 @@ See [i18n-tasks wiki: CSV import and export tasks](https://github.com/glebm/i18n
436
436
  Tasks that come with the gem are defined in [lib/i18n/tasks/command/commands](lib/i18n/tasks/command/commands).
437
437
  Custom tasks can be added easily, see the examples [on the wiki](https://github.com/glebm/i18n-tasks/wiki#custom-tasks).
438
438
 
439
+ # Development
440
+
441
+ - Install dependencies using `bundle install`
442
+ - Run tests using `bundle exec rspec`
443
+ - Install [Overcommit](overcommit) by running `overcommit --install`
444
+
445
+ ## Skip Overcommit-hooks
446
+
447
+ - `SKIP=RuboCop git commit`
448
+ - `OVERCOMMIT_DISABLE=1 git commit`
449
+
450
+
439
451
  [MIT license]: /LICENSE.txt
440
452
  [ci]: https://github.com/glebm/i18n-tasks/actions/workflows/tests.yml
441
453
  [badge-ci]: https://github.com/glebm/i18n-tasks/actions/workflows/tests.yml/badge.svg
442
454
  [coverage]: https://codeclimate.com/github/glebm/i18n-tasks
443
455
  [badge-coverage]: https://api.codeclimate.com/v1/badges/5d173e90ada8df07cedc/test_coverage
444
- [config]: https://github.com/glebm/i18n-tasks/blob/master/templates/config/i18n-tasks.yml
456
+ [config]: https://github.com/glebm/i18n-tasks/blob/main/templates/config/i18n-tasks.yml
445
457
  [wiki]: https://github.com/glebm/i18n-tasks/wiki "i18n-tasks wiki"
446
458
  [i18n-gem]: https://github.com/svenfuchs/i18n "svenfuchs/i18n on Github"
447
459
  [screenshot-i18n-tasks]: https://i.imgur.com/XZBd8l7.png "i18n-tasks screenshot"
448
460
  [screenshot-find]: https://i.imgur.com/VxBrSfY.png "i18n-tasks find output screenshot"
449
- [adapter-example]: https://github.com/glebm/i18n-tasks/blob/master/lib/i18n/tasks/data/file_system_base.rb
461
+ [adapter-example]: https://github.com/glebm/i18n-tasks/blob/main/lib/i18n/tasks/data/file_system_base.rb
450
462
  [custom-scanner-docs]: https://github.com/glebm/i18n-tasks/wiki/A-custom-scanner-example
463
+ [overcommit]: https://github.com/sds/overcommit#installation
data/i18n-tasks.gemspec CHANGED
@@ -22,10 +22,15 @@ Gem::Specification.new do |s| # rubocop:disable Metrics/BlockLength
22
22
  cp $(bundle exec i18n-tasks gem-path)/templates/config/i18n-tasks.yml config/
23
23
  # Add an RSpec for missing and unused keys:
24
24
  cp $(bundle exec i18n-tasks gem-path)/templates/rspec/i18n_spec.rb spec/
25
+ # Or for minitest:
26
+ cp $(bundle exec i18n-tasks gem-path)/templates/minitest/i18n_test.rb test/
25
27
  TEXT
26
28
  s.homepage = 'https://github.com/glebm/i18n-tasks'
27
- s.metadata = { 'issue_tracker' => 'https://github.com/glebm/i18n-tasks' } if s.respond_to?(:metadata=)
28
- s.required_ruby_version = '>= 2.6', '< 4.0' if s.respond_to?(:required_ruby_version=)
29
+ s.metadata = {
30
+ 'issue_tracker' => 'https://github.com/glebm/i18n-tasks',
31
+ 'rubygems_mfa_required' => 'true'
32
+ }
33
+ s.required_ruby_version = '>= 2.6', '< 4.0'
29
34
 
30
35
  s.files = `git ls-files`.split($/)
31
36
  s.files -= s.files.grep(%r{^(doc/|\.|spec/)}) + %w[CHANGES.md config/i18n-tasks.yml Gemfile]
@@ -43,11 +48,11 @@ Gem::Specification.new do |s| # rubocop:disable Metrics/BlockLength
43
48
  s.add_dependency 'rails-i18n'
44
49
  s.add_dependency 'rainbow', '>= 2.2.2', '< 4.0'
45
50
  s.add_dependency 'terminal-table', '>= 1.5.1'
46
- s.add_development_dependency 'axlsx', '~> 2.0'
47
51
  s.add_development_dependency 'bundler', '~> 2.0', '>= 2.0.1'
52
+ s.add_development_dependency 'overcommit', '~> 0.58.0'
48
53
  s.add_development_dependency 'rake'
49
54
  s.add_development_dependency 'rspec', '~> 3.3'
50
- s.add_development_dependency 'rubocop', '~> 1.6.1'
55
+ s.add_development_dependency 'rubocop', '~> 1.27.0'
51
56
  s.add_development_dependency 'simplecov'
52
57
  s.add_development_dependency 'yard'
53
58
 
@@ -35,12 +35,13 @@ class I18n::Tasks::CLI
35
35
 
36
36
  def run(argv)
37
37
  argv.each_with_index do |arg, i|
38
- if ["--config", "-c"].include?(arg)
39
- if File.exist?(argv[i+1])
40
- @config_file = argv[i+1]
38
+ if ['--config', '-c'].include?(arg)
39
+ _, config_file = argv.slice!(i, 2)
40
+ if File.exist?(config_file)
41
+ @config_file = config_file
41
42
  break
42
43
  else
43
- error "Config file doesn't exist: #{argv[i+1]}", 128
44
+ error "Config file doesn't exist: #{config_file}", 128
44
45
  end
45
46
  end
46
47
  end
@@ -33,7 +33,6 @@ module I18n::Tasks
33
33
  '--config FILE',
34
34
  t('i18n_tasks.cmd.args.desc.config')
35
35
 
36
-
37
36
  def arg_or_pos!(key, opts)
38
37
  opts[key] ||= opts[:arguments].try(:shift)
39
38
  end
@@ -86,7 +86,7 @@ module I18n::Tasks
86
86
  when 'keys'
87
87
  puts forest.key_names(root: true)
88
88
  when 'key-values'
89
- puts forest.key_values(root: true).map { |kv| kv.join("\t") }
89
+ puts(forest.key_values(root: true).map { |kv| kv.join("\t") })
90
90
  when *DATA_FORMATS
91
91
  puts i18n.data.adapter_dump forest.to_hash(true), format
92
92
  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.
@@ -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] ||= begin
48
- {
49
- adapter: data.class.name,
50
- config: data.config
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
@@ -5,7 +5,7 @@ module I18n::Tasks
5
5
  module Data
6
6
  module Adapter
7
7
  module YamlAdapter
8
- EMOJI_REGEX = /\\u[\da-f]{8}/i
8
+ EMOJI_REGEX = /\\u[\da-f]{8}/i.freeze
9
9
 
10
10
  class << self
11
11
  # @return [Hash] locale tree
@@ -25,7 +25,7 @@ module I18n::Tasks
25
25
 
26
26
  # @return [String]
27
27
  def restore_emojis(yaml)
28
- yaml.gsub(EMOJI_REGEX) { |m| [m[-8..].to_i(16)].pack("U") }
28
+ yaml.gsub(EMOJI_REGEX) { |m| [m[-8..].to_i(16)].pack('U') }
29
29
  end
30
30
  end
31
31
  end
@@ -55,7 +55,7 @@ module I18n
55
55
  return if File.file?(path) && content == read_file(path)
56
56
 
57
57
  ::FileUtils.mkpath(File.dirname(path))
58
- ::File.open(path, 'w') { |f| f.write content }
58
+ ::File.write(path, content)
59
59
  end
60
60
 
61
61
  def normalized?(path, tree)
@@ -38,7 +38,7 @@ module I18n::Tasks
38
38
  if pattern
39
39
  key_match = $~
40
40
  path = format(path, locale: locale)
41
- path.gsub!(/\\\d+/) { |m| key_match[m[1..-1].to_i] }
41
+ path.gsub!(/\\\d+/) { |m| key_match[m[1..].to_i] }
42
42
  (out[path] ||= Set.new) << "#{locale}.#{key}"
43
43
  else
44
44
  fail CommandError, "Cannot route key #{key}. Routes are #{@routes_config.inspect}"
@@ -168,7 +168,7 @@ module I18n::Tasks::Data::Tree
168
168
  label = if key.nil?
169
169
  Rainbow('∅').faint
170
170
  else
171
- [Rainbow(key).color(1 + level % 15),
171
+ [Rainbow(key).color(1 + (level % 15)),
172
172
  (": #{format_value_for_inspect(value)}" if leaf?),
173
173
  (" #{data}" if data?)].compact.join
174
174
  end
@@ -31,13 +31,11 @@ module I18n::Tasks::Data::Tree
31
31
  end
32
32
 
33
33
  def to_hash(sort = false)
34
- (@hash ||= {})[sort] ||= begin
35
- if sort
36
- sort_by(&:key)
37
- else
38
- self
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
@@ -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..-1].to_i] }
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('.')] =
@@ -7,8 +7,8 @@ module I18n::Tasks
7
7
 
8
8
  def html_key?(full_key, locale)
9
9
  !!(full_key =~ HTML_KEY_PATTERN ||
10
- full_key =~ MAYBE_PLURAL_HTML_KEY_PATTERN &&
11
- depluralize_key(split_key(full_key, 2)[1], locale) =~ HTML_KEY_PATTERN)
10
+ (full_key =~ MAYBE_PLURAL_HTML_KEY_PATTERN &&
11
+ depluralize_key(split_key(full_key, 2)[1], locale) =~ HTML_KEY_PATTERN))
12
12
  end
13
13
  end
14
14
  end
@@ -91,8 +91,8 @@ module I18n::Tasks
91
91
  if node.value != other.value
92
92
  log_warn(
93
93
  'Conflicting references: '\
94
- "#{node.full_key(root: false)} ⮕ #{node.value} in #{node.data[:locale]},"\
95
- " but ⮕ #{other.value} in #{other.data[:locale]}"
94
+ "#{node.full_key(root: false)} ⮕ #{node.value} in #{node.data[:locale]},"\
95
+ " but ⮕ #{other.value} in #{other.data[:locale]}"
96
96
  )
97
97
  end
98
98
  end
@@ -37,7 +37,7 @@ module I18n::Tasks::Reports
37
37
  def used_title(keys_nodes, filter)
38
38
  used_n = keys_nodes.map { |_k, node| node.data[:occurrences].size }.reduce(:+).to_i
39
39
  "#{keys_nodes.size} key#{'s' if keys_nodes.size != 1}#{" matching '#{filter}'" if filter}"\
40
- "#{" (#{used_n} usage#{'s' if used_n != 1})" if used_n.positive?}"
40
+ "#{" (#{used_n} usage#{'s' if used_n != 1})" if used_n.positive?}"
41
41
  end
42
42
 
43
43
  # Sort keys by their attributes in order
@@ -121,9 +121,9 @@ module I18n
121
121
  if data[:ref_info]
122
122
  from, to = data[:ref_info]
123
123
  resolved = key[0...to.length]
124
- after = key[to.length..-1]
124
+ after = key[to.length..]
125
125
  " #{Rainbow(from).yellow}#{Rainbow(after).cyan}\n" \
126
- "#{Rainbow('⮕').yellow.bright} #{Rainbow(resolved).yellow.bright}"
126
+ "#{Rainbow('⮕').yellow.bright} #{Rainbow(resolved).yellow.bright}"
127
127
  else
128
128
  Rainbow(key).cyan
129
129
  end
@@ -6,6 +6,10 @@ module I18n::Tasks::Scanners::AstMatchers
6
6
  @scanner = scanner
7
7
  end
8
8
 
9
+ def convert_to_key_occurrences(send_node, _method_name, location: send_node.loc)
10
+ fail('Not implemented')
11
+ end
12
+
9
13
  protected
10
14
 
11
15
  # If the node type is of `%i(sym str int false true)`, return the value as a string.
@@ -20,7 +24,7 @@ module I18n::Tasks::Scanners::AstMatchers
20
24
  # No effect unless `array_join_with` is set.
21
25
  # @return [String, nil] `nil` is returned only when a dynamic value is encountered in strict mode
22
26
  # or the node type is not supported.
23
- def extract_string(node, array_join_with: nil, array_flatten: false, array_reject_blank: false)
27
+ def extract_string(node, array_join_with: nil, array_flatten: false, array_reject_blank: false) # rubocop:disable Metrics/AbcSize, Metrics/PerceivedComplexity
24
28
  return if node.nil?
25
29
 
26
30
  if %i[sym str int].include?(node.type)
@@ -0,0 +1,69 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'i18n/tasks/scanners/results/occurrence'
4
+
5
+ module I18n::Tasks::Scanners::AstMatchers
6
+ class RailsModelMatcher < BaseMatcher
7
+ def convert_to_key_occurrences(send_node, _method_name, location: send_node.loc)
8
+ human_attribute_name_to_key_occurences(send_node: send_node, location: location) ||
9
+ model_name_human_to_key_occurences(send_node: send_node, location: location)
10
+ end
11
+
12
+ private
13
+
14
+ def human_attribute_name_to_key_occurences(send_node:, location:)
15
+ children = Array(send_node&.children)
16
+ receiver = children[0]
17
+ method_name = children[1]
18
+
19
+ return unless method_name == :human_attribute_name && receiver.type == :const
20
+
21
+ value = children[2]
22
+
23
+ model_name = underscore(receiver.to_a.last)
24
+ attribute = extract_string(value)
25
+ key = "activerecord.attributes.#{model_name}.#{attribute}"
26
+ [
27
+ key,
28
+ I18n::Tasks::Scanners::Results::Occurrence.from_range(
29
+ raw_key: key,
30
+ range: location.expression
31
+ )
32
+ ]
33
+ end
34
+
35
+ # User.model_name.human(count: 2)
36
+ # s(:send,
37
+ # s(:send,
38
+ # s(:const, nil, :User), :model_name), :human,
39
+ # s(:hash,
40
+ # s(:pair,
41
+ # s(:sym, :count),
42
+ # s(:int, 2))))
43
+ def model_name_human_to_key_occurences(send_node:, location:)
44
+ children = Array(send_node&.children)
45
+ return unless children[1] == :human
46
+
47
+ base_children = Array(children[0]&.children)
48
+ class_node = base_children[0]
49
+
50
+ return unless class_node&.type == :const && base_children[1] == :model_name
51
+
52
+ model_name = underscore(class_node.to_a.last)
53
+ key = "activerecord.models.#{model_name}"
54
+ [
55
+ key,
56
+ I18n::Tasks::Scanners::Results::Occurrence.from_range(
57
+ raw_key: key,
58
+ range: location.expression
59
+ )
60
+ ]
61
+ end
62
+
63
+ def underscore(value)
64
+ value = value.dup.to_s
65
+ value.gsub!(/(.)([A-Z])/, '\1_\2')
66
+ value.downcase!
67
+ end
68
+ end
69
+ end
@@ -35,7 +35,7 @@ module I18n::Tasks::Scanners
35
35
  result = []
36
36
  text.scan(pattern) do |_|
37
37
  match = Regexp.last_match
38
- matches = Hash[match.names.map(&:to_sym).zip(match.captures)]
38
+ matches = match.names.map(&:to_sym).zip(match.captures).to_h
39
39
  if matches.key?(:key)
40
40
  matches[:key] = strip_literal(matches[:key])
41
41
  next unless valid_key?(matches[:key])
@@ -66,7 +66,7 @@ module I18n::Tasks::Scanners
66
66
  end
67
67
 
68
68
  def exclude_line?(line, path)
69
- re = @ignore_lines_res[File.extname(path)[1..-1]]
69
+ re = @ignore_lines_res[File.extname(path)[1..]]
70
70
  re && re =~ line
71
71
  end
72
72
 
@@ -10,7 +10,7 @@ module I18n
10
10
  # @param calling_method [#call, Symbol, String, false, nil]
11
11
  # @return [String] absolute version of the key
12
12
  def absolute_key(key, path, roots: config[:relative_roots],
13
- exclude_method_name_paths: config[:exclude_method_name_paths],
13
+ exclude_method_name_paths: config[:relative_exclude_method_name_paths],
14
14
  calling_method: nil)
15
15
  return key unless key.start_with?(DOT)
16
16
  fail 'roots argument is required' unless roots.present?
@@ -18,7 +18,7 @@ module I18n
18
18
  normalized_path = File.expand_path(path)
19
19
  (root = path_root(normalized_path, roots)) ||
20
20
  fail(CommandError, "Cannot resolve relative key \"#{key}\".\n" \
21
- "Set search.relative_roots in config/i18n-tasks.yml (currently #{roots.inspect})")
21
+ "Set search.relative_roots in config/i18n-tasks.yml (currently #{roots.inspect})")
22
22
  normalized_path.sub!(root, '')
23
23
 
24
24
  if (exclude_method_name_paths || []).map { |p| expand_path(p) }.include?(root)
@@ -48,7 +48,7 @@ module I18n::Tasks
48
48
  # rubocop:enable Metrics/ParameterLists
49
49
 
50
50
  def inspect
51
- "Occurrence(#{@path}:#{@line_num}, line_pos: #{@line_pos}, pos: #{@pos}, raw_key: #{@raw_key}, default_arg: #{@default_arg}, line: #{@line})"
51
+ "Occurrence(#{@path}:#{@line_num}, line_pos: #{@line_pos}, pos: #{@pos}, raw_key: #{@raw_key}, default_arg: #{@default_arg}, line: #{@line})" # rubocop:disable Layout/LineLength
52
52
  end
53
53
 
54
54
  def ==(other)
@@ -68,8 +68,7 @@ module I18n::Tasks::Scanners
68
68
  def comments_to_occurences(path, ast, comments)
69
69
  magic_comments = comments.select { |comment| comment.text =~ MAGIC_COMMENT_PREFIX }
70
70
  comment_to_node = Parser::Source::Comment.associate_locations(ast, magic_comments).tap do |h|
71
- # transform_values is only available in ActiveSupport 4.2+
72
- h.each { |k, v| h[k] = v.first }
71
+ h.transform_values!(&:first)
73
72
  end.invert
74
73
 
75
74
  magic_comments.flat_map do |comment|
@@ -122,7 +121,7 @@ module I18n::Tasks::Scanners
122
121
  )
123
122
  end
124
123
  else
125
- %i[t t! translate translate!].map do |message|
124
+ matchers = %i[t t! translate translate!].map do |message|
126
125
  AstMatchers::MessageReceiversMatcher.new(
127
126
  receivers: [
128
127
  AST::Node.new(:const, [nil, :I18n]),
@@ -132,6 +131,12 @@ module I18n::Tasks::Scanners
132
131
  scanner: self
133
132
  )
134
133
  end
134
+
135
+ Array(config[:ast_matchers]).each do |class_name|
136
+ matchers << ActiveSupport::Inflector.constantize(class_name).new(scanner: self)
137
+ end
138
+
139
+ matchers
135
140
  end
136
141
  end
137
142
  end
@@ -15,12 +15,12 @@ module I18n::Tasks::Scanners
15
15
  # @param literal [String] e.g: "key", 'key', or :key.
16
16
  # @return [String] key
17
17
  def strip_literal(literal)
18
- literal = literal[1..-1] if literal[0] == ':'
18
+ literal = literal[1..] if literal[0] == ':'
19
19
  literal = literal[1..-2] if literal[0] == "'" || literal[0] == '"'
20
20
  literal
21
21
  end
22
22
 
23
- VALID_KEY_CHARS = /(?:[[:word:]]|[-.?!:;À-ž\/])/.freeze
23
+ VALID_KEY_CHARS = %r{(?:[[:word:]]|[-.?!:;À-ž/])}.freeze
24
24
  VALID_KEY_RE = /^#{VALID_KEY_CHARS}+$/.freeze
25
25
 
26
26
  def valid_key?(key)
@@ -20,7 +20,7 @@ module I18n
20
20
  parts << part
21
21
  pos += part.length + 1
22
22
  if parts.length + 1 >= max
23
- parts << key[pos..-1] unless pos == key.length
23
+ parts << key[pos..] unless pos == key.length
24
24
  break
25
25
  end
26
26
  end
@@ -114,7 +114,7 @@ module I18n::Tasks
114
114
 
115
115
  values = untranslated.scan(INTERPOLATION_KEY_RE)
116
116
  translated.gsub(/#{Regexp.escape(UNTRANSLATABLE_STRING)}\d+/i) do |m|
117
- values[m[UNTRANSLATABLE_STRING.length..-1].to_i]
117
+ values[m[UNTRANSLATABLE_STRING.length..].to_i]
118
118
  end
119
119
  rescue StandardError => e
120
120
  raise_interpolation_error(untranslated, translated, e)
@@ -71,11 +71,11 @@ module I18n::Tasks::Translators
71
71
  version = @i18n_tasks.translation_config[:deepl_version]
72
72
  fail ::I18n::Tasks::CommandError, I18n.t('i18n_tasks.deepl_translate.errors.no_api_key') if api_key.blank?
73
73
 
74
- DeepL.configure { |config|
74
+ DeepL.configure do |config|
75
75
  config.auth_key = api_key
76
76
  config.host = host unless host.blank?
77
77
  config.version = version unless version.blank?
78
- }
78
+ end
79
79
  end
80
80
  end
81
81
  end
@@ -24,12 +24,13 @@ module I18n::Tasks
24
24
  ['::I18n::Tasks::Scanners::ErbAstScanner', { only: %w[*.erb] }],
25
25
  ['::I18n::Tasks::Scanners::PatternWithScopeScanner', { exclude: %w[*.erb *.rb] }]
26
26
  ],
27
+ ast_matchers: [],
27
28
  strict: true
28
29
  }.freeze
29
30
 
30
31
  ALWAYS_EXCLUDE = %w[*.jpg *.jpeg *.png *.gif *.svg *.ico *.eot *.otf *.ttf *.woff *.woff2 *.pdf *.css *.sass *.scss *.less
31
32
  *.yml *.json *.zip *.tar.gz *.swf *.flv *.mp3 *.wav *.flac *.webm *.mp4 *.ogg *.opus
32
- *.webp *.map].freeze
33
+ *.webp *.map *.xlsx].freeze
33
34
 
34
35
  # Find all keys in the source and return a forest with the keys in absolute form and their occurrences.
35
36
  #
@@ -177,7 +178,7 @@ module I18n::Tasks
177
178
  braces << '{'
178
179
  end
179
180
  end
180
- result << key[scanner.pos..-1] unless scanner.eos?
181
+ result << key[scanner.pos..] unless scanner.eos?
181
182
  result.join
182
183
  end
183
184
  end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module I18n
4
4
  module Tasks
5
- VERSION = '1.0.8'
5
+ VERSION = '1.0.11'
6
6
  end
7
7
  end
data/lib/i18n/tasks.rb CHANGED
@@ -34,6 +34,17 @@ module I18n
34
34
  ::I18n::Tasks::Commands.send :include, commands_module
35
35
  self
36
36
  end
37
+
38
+ # Add AST-matcher to i18n-tasks
39
+ #
40
+ # @param matcher_class_name
41
+ # @return self
42
+ def add_ast_matcher(matcher_class_name)
43
+ matchers = I18n::Tasks::Configuration::DEFAULTS[:search][:ast_matchers]
44
+ matchers << matcher_class_name
45
+ matchers.uniq!
46
+ self
47
+ end
37
48
  end
38
49
 
39
50
  @verbose = !ENV['VERBOSE'].nil?
@@ -72,11 +72,13 @@ search:
72
72
  # -
73
73
 
74
74
  ## Files or `File.fnmatch` patterns to exclude from search. Some files are always excluded regardless of this setting:
75
- ## %w(*.jpg *.png *.gif *.svg *.ico *.eot *.otf *.ttf *.woff *.woff2 *.pdf *.css *.sass *.scss *.less *.yml *.json *.map)
75
+ ## *.jpg *.jpeg *.png *.gif *.svg *.ico *.eot *.otf *.ttf *.woff *.woff2 *.pdf *.css *.sass *.scss *.less
76
+ ## *.yml *.json *.zip *.tar.gz *.swf *.flv *.mp3 *.wav *.flac *.webm *.mp4 *.ogg *.opus *.webp *.map *.xlsx
76
77
  exclude:
77
78
  - app/assets/images
78
79
  - app/assets/fonts
79
80
  - app/assets/videos
81
+ - app/assets/builds
80
82
 
81
83
  ## Alternatively, the only files or `File.fnmatch patterns` to search in `paths`:
82
84
  ## If specified, this settings takes priority over `exclude`, but `exclude` still applies.
@@ -85,6 +87,15 @@ search:
85
87
  ## If `strict` is `false`, guess usages such as t("categories.#{category}.title"). The default is `true`.
86
88
  # strict: true
87
89
 
90
+ ## Allows adding ast_matchers for finding translations using the AST-scanners
91
+ ## The available matchers are:
92
+ ## - RailsModelMatcher
93
+ ## Matches ActiveRecord translations like
94
+ ## User.human_attribute_name(:email) and User.model_name.human
95
+ ##
96
+ ## To implement your own, please see `I18n::Tasks::Scanners::AstMatchers::BaseMatcher`.
97
+ <%# I18n::Tasks.add_ast_matcher('I18n::Tasks::Scanners::AstMatchers::RailsModelMatcher') %>
98
+
88
99
  ## Multiple scanners can be used. Their results are merged.
89
100
  ## The options specified above are passed down to each scanner. Per-scanner options can be specified as well.
90
101
  ## See this example of a custom scanner: https://github.com/glebm/i18n-tasks/wiki/A-custom-scanner-example
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: 1.0.8
4
+ version: 1.0.11
5
5
  platform: ruby
6
6
  authors:
7
7
  - glebm
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-04-06 00:00:00.000000000 Z
11
+ date: 2022-06-15 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -157,12 +157,15 @@ dependencies:
157
157
  - !ruby/object:Gem::Version
158
158
  version: 1.5.1
159
159
  - !ruby/object:Gem::Dependency
160
- name: axlsx
160
+ name: bundler
161
161
  requirement: !ruby/object:Gem::Requirement
162
162
  requirements:
163
163
  - - "~>"
164
164
  - !ruby/object:Gem::Version
165
165
  version: '2.0'
166
+ - - ">="
167
+ - !ruby/object:Gem::Version
168
+ version: 2.0.1
166
169
  type: :development
167
170
  prerelease: false
168
171
  version_requirements: !ruby/object:Gem::Requirement
@@ -170,26 +173,23 @@ dependencies:
170
173
  - - "~>"
171
174
  - !ruby/object:Gem::Version
172
175
  version: '2.0'
176
+ - - ">="
177
+ - !ruby/object:Gem::Version
178
+ version: 2.0.1
173
179
  - !ruby/object:Gem::Dependency
174
- name: bundler
180
+ name: overcommit
175
181
  requirement: !ruby/object:Gem::Requirement
176
182
  requirements:
177
183
  - - "~>"
178
184
  - !ruby/object:Gem::Version
179
- version: '2.0'
180
- - - ">="
181
- - !ruby/object:Gem::Version
182
- version: 2.0.1
185
+ version: 0.58.0
183
186
  type: :development
184
187
  prerelease: false
185
188
  version_requirements: !ruby/object:Gem::Requirement
186
189
  requirements:
187
190
  - - "~>"
188
191
  - !ruby/object:Gem::Version
189
- version: '2.0'
190
- - - ">="
191
- - !ruby/object:Gem::Version
192
- version: 2.0.1
192
+ version: 0.58.0
193
193
  - !ruby/object:Gem::Dependency
194
194
  name: rake
195
195
  requirement: !ruby/object:Gem::Requirement
@@ -224,14 +224,14 @@ dependencies:
224
224
  requirements:
225
225
  - - "~>"
226
226
  - !ruby/object:Gem::Version
227
- version: 1.6.1
227
+ version: 1.27.0
228
228
  type: :development
229
229
  prerelease: false
230
230
  version_requirements: !ruby/object:Gem::Requirement
231
231
  requirements:
232
232
  - - "~>"
233
233
  - !ruby/object:Gem::Version
234
- version: 1.6.1
234
+ version: 1.27.0
235
235
  - !ruby/object:Gem::Dependency
236
236
  name: simplecov
237
237
  requirement: !ruby/object:Gem::Requirement
@@ -373,6 +373,7 @@ files:
373
373
  - lib/i18n/tasks/reports/terminal.rb
374
374
  - lib/i18n/tasks/scanners/ast_matchers/base_matcher.rb
375
375
  - lib/i18n/tasks/scanners/ast_matchers/message_receivers_matcher.rb
376
+ - lib/i18n/tasks/scanners/ast_matchers/rails_model_matcher.rb
376
377
  - lib/i18n/tasks/scanners/erb_ast_processor.rb
377
378
  - lib/i18n/tasks/scanners/erb_ast_scanner.rb
378
379
  - lib/i18n/tasks/scanners/file_scanner.rb
@@ -413,11 +414,14 @@ licenses:
413
414
  - MIT
414
415
  metadata:
415
416
  issue_tracker: https://github.com/glebm/i18n-tasks
417
+ rubygems_mfa_required: 'true'
416
418
  post_install_message: |
417
419
  # Install default configuration:
418
420
  cp $(bundle exec i18n-tasks gem-path)/templates/config/i18n-tasks.yml config/
419
421
  # Add an RSpec for missing and unused keys:
420
422
  cp $(bundle exec i18n-tasks gem-path)/templates/rspec/i18n_spec.rb spec/
423
+ # Or for minitest:
424
+ cp $(bundle exec i18n-tasks gem-path)/templates/minitest/i18n_test.rb test/
421
425
  rdoc_options: []
422
426
  require_paths:
423
427
  - lib