i18n-tasks 1.0.8 → 1.0.11

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 (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