i18n-tasks 0.9.31 → 0.9.35

Sign up to get free protection for your applications and to get access to all the features.
Files changed (61) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +7 -7
  3. data/config/locales/en.yml +1 -0
  4. data/i18n-tasks.gemspec +4 -4
  5. data/lib/i18n/tasks/base_task.rb +2 -1
  6. data/lib/i18n/tasks/cli.rb +26 -17
  7. data/lib/i18n/tasks/command/commands/data.rb +8 -6
  8. data/lib/i18n/tasks/command/commands/eq_base.rb +2 -2
  9. data/lib/i18n/tasks/command/commands/health.rb +4 -3
  10. data/lib/i18n/tasks/command/commands/interpolations.rb +1 -1
  11. data/lib/i18n/tasks/command/commands/meta.rb +1 -1
  12. data/lib/i18n/tasks/command/commands/missing.rb +7 -6
  13. data/lib/i18n/tasks/command/commands/tree.rb +8 -6
  14. data/lib/i18n/tasks/command/commands/usages.rb +5 -4
  15. data/lib/i18n/tasks/command/dsl.rb +4 -4
  16. data/lib/i18n/tasks/command/option_parsers/enum.rb +2 -0
  17. data/lib/i18n/tasks/command/option_parsers/locale.rb +2 -1
  18. data/lib/i18n/tasks/command/options/common.rb +6 -0
  19. data/lib/i18n/tasks/command/options/data.rb +1 -0
  20. data/lib/i18n/tasks/command/options/locales.rb +5 -5
  21. data/lib/i18n/tasks/concurrent/cached_value.rb +2 -0
  22. data/lib/i18n/tasks/configuration.rb +7 -5
  23. data/lib/i18n/tasks/console_context.rb +1 -1
  24. data/lib/i18n/tasks/data/file_formats.rb +2 -0
  25. data/lib/i18n/tasks/data/file_system_base.rb +7 -6
  26. data/lib/i18n/tasks/data/router/conservative_router.rb +2 -1
  27. data/lib/i18n/tasks/data/router/pattern_router.rb +2 -0
  28. data/lib/i18n/tasks/data/tree/node.rb +5 -2
  29. data/lib/i18n/tasks/data/tree/nodes.rb +1 -0
  30. data/lib/i18n/tasks/data/tree/siblings.rb +9 -2
  31. data/lib/i18n/tasks/data/tree/traversal.rb +11 -2
  32. data/lib/i18n/tasks/html_keys.rb +2 -4
  33. data/lib/i18n/tasks/ignore_keys.rb +4 -3
  34. data/lib/i18n/tasks/interpolations.rb +4 -2
  35. data/lib/i18n/tasks/key_pattern_matching.rb +3 -2
  36. data/lib/i18n/tasks/missing_keys.rb +34 -7
  37. data/lib/i18n/tasks/plural_keys.rb +6 -1
  38. data/lib/i18n/tasks/references.rb +2 -0
  39. data/lib/i18n/tasks/reports/base.rb +3 -2
  40. data/lib/i18n/tasks/reports/terminal.rb +6 -4
  41. data/lib/i18n/tasks/scanners/file_scanner.rb +4 -3
  42. data/lib/i18n/tasks/scanners/files/caching_file_finder.rb +0 -3
  43. data/lib/i18n/tasks/scanners/files/file_finder.rb +3 -2
  44. data/lib/i18n/tasks/scanners/occurrence_from_position.rb +3 -3
  45. data/lib/i18n/tasks/scanners/pattern_scanner.rb +7 -4
  46. data/lib/i18n/tasks/scanners/pattern_with_scope_scanner.rb +4 -2
  47. data/lib/i18n/tasks/scanners/relative_keys.rb +18 -3
  48. data/lib/i18n/tasks/scanners/ruby_ast_scanner.rb +16 -13
  49. data/lib/i18n/tasks/scanners/ruby_key_literals.rb +3 -3
  50. data/lib/i18n/tasks/scanners/scanner_multiplexer.rb +2 -0
  51. data/lib/i18n/tasks/split_key.rb +2 -0
  52. data/lib/i18n/tasks/string_interpolation.rb +1 -0
  53. data/lib/i18n/tasks/translation.rb +3 -3
  54. data/lib/i18n/tasks/translators/base_translator.rb +4 -2
  55. data/lib/i18n/tasks/translators/deepl_translator.rb +16 -3
  56. data/lib/i18n/tasks/translators/google_translator.rb +2 -0
  57. data/lib/i18n/tasks/translators/yandex_translator.rb +2 -0
  58. data/lib/i18n/tasks/used_keys.rb +18 -13
  59. data/lib/i18n/tasks/version.rb +1 -1
  60. data/templates/config/i18n-tasks.yml +10 -1
  61. metadata +10 -7
@@ -26,6 +26,7 @@ module I18n::Tasks::Scanners
26
26
  if scope
27
27
  scope_parts = extract_literal_or_array_of_literals(scope)
28
28
  return nil if scope_parts.nil? || scope_parts.empty?
29
+
29
30
  "#{scope_parts.join('.')}.#{key}"
30
31
  else
31
32
  key unless match[0] =~ /\A\w/
@@ -61,7 +62,7 @@ module I18n::Tasks::Scanners
61
62
 
62
63
  # extract literal or array of literals
63
64
  # returns nil on any other input
64
- # rubocop:disable Metrics/CyclomaticComplexity,Metrics/MethodLength,Metrics/PerceivedComplexity
65
+ # rubocop:disable Metrics/MethodLength,Metrics/PerceivedComplexity
65
66
  def extract_literal_or_array_of_literals(s)
66
67
  literals = []
67
68
  braces_stack = []
@@ -78,6 +79,7 @@ module I18n::Tasks::Scanners
78
79
  s.each_char.with_index do |c, i|
79
80
  if c == '['
80
81
  return nil unless braces_stack.empty?
82
+
81
83
  braces_stack.push(i)
82
84
  elsif c == ']'
83
85
  break
@@ -93,6 +95,6 @@ module I18n::Tasks::Scanners
93
95
  consume_literal.call unless acc.empty?
94
96
  literals
95
97
  end
96
- # rubocop:enable Metrics/CyclomaticComplexity,Metrics/MethodLength,Metrics/PerceivedComplexity
98
+ # rubocop:enable Metrics/MethodLength,Metrics/PerceivedComplexity
97
99
  end
98
100
  end
@@ -9,15 +9,23 @@ module I18n
9
9
  # @param roots [Array<String>] paths to relative roots
10
10
  # @param calling_method [#call, Symbol, String, false, nil]
11
11
  # @return [String] absolute version of the key
12
- def absolute_key(key, path, roots: config[:relative_roots], calling_method: nil)
12
+ def absolute_key(key, path, roots: config[:relative_roots],
13
+ exclude_method_name_paths: config[:exclude_method_name_paths],
14
+ calling_method: nil)
13
15
  return key unless key.start_with?(DOT)
14
16
  fail 'roots argument is required' unless roots.present?
17
+
15
18
  normalized_path = File.expand_path(path)
16
19
  (root = path_root(normalized_path, roots)) ||
17
20
  fail(CommandError, "Cannot resolve relative key \"#{key}\".\n" \
18
21
  "Set search.relative_roots in config/i18n-tasks.yml (currently #{roots.inspect})")
19
22
  normalized_path.sub!(root, '')
20
- "#{prefix(normalized_path, calling_method: calling_method)}#{key}"
23
+
24
+ if (exclude_method_name_paths || []).map { |p| expand_path(p) }.include?(root)
25
+ "#{prefix(normalized_path)}#{key}"
26
+ else
27
+ "#{prefix(normalized_path, calling_method: calling_method)}#{key}"
28
+ end
21
29
  end
22
30
 
23
31
  private
@@ -30,12 +38,19 @@ module I18n
30
38
  # @return [String] the closest ancestor root for path, with a trailing {File::SEPARATOR}.
31
39
  def path_root(path, roots)
32
40
  roots.map do |p|
33
- File.expand_path(p) + File::SEPARATOR
41
+ expand_path(p)
34
42
  end.sort.reverse_each.detect do |root|
35
43
  path.start_with?(root)
36
44
  end
37
45
  end
38
46
 
47
+ # Expand a path and add a trailing {File::SEPARATOR}
48
+ # @param [String] path relative path
49
+ # @return [String] absolute path, with a trailing {File::SEPARATOR}.
50
+ def expand_path(path)
51
+ File.expand_path(path) + File::SEPARATOR
52
+ end
53
+
39
54
  # @param normalized_path [String] path/relative/to/a/root
40
55
  # @param calling_method [#call, Symbol, String, false, nil]
41
56
  def prefix(normalized_path, calling_method: nil)
@@ -5,7 +5,7 @@ require 'i18n/tasks/scanners/relative_keys'
5
5
  require 'i18n/tasks/scanners/ruby_ast_call_finder'
6
6
  require 'parser/current'
7
7
 
8
- # rubocop:disable Metrics/AbcSize,Metrics/BlockNesting,Metrics/CyclomaticComplexity,Metrics/PerceivedComplexity
8
+ # rubocop:disable Metrics/AbcSize,Metrics/BlockNesting,Metrics/PerceivedComplexity
9
9
  # TODO: make this class more readable.
10
10
 
11
11
  module I18n::Tasks::Scanners
@@ -14,7 +14,7 @@ module I18n::Tasks::Scanners
14
14
  include RelativeKeys
15
15
  include AST::Sexp
16
16
 
17
- MAGIC_COMMENT_PREFIX = /\A.\s*i18n-tasks-use\s+/
17
+ MAGIC_COMMENT_PREFIX = /\A.\s*i18n-tasks-use\s+/.freeze
18
18
  RECEIVER_MESSAGES = [nil, AST::Node.new(:const, [nil, :I18n])].product(%i[t t! translate translate!])
19
19
 
20
20
  def initialize(**args)
@@ -71,6 +71,7 @@ module I18n::Tasks::Scanners
71
71
  scope = extract_string(scope_node.children[1],
72
72
  array_join_with: '.', array_flatten: true, array_reject_blank: true)
73
73
  return nil if scope.nil? && scope_node.type != :nil
74
+
74
75
  key = [scope, key].join('.') unless scope == ''
75
76
  end
76
77
  default_arg = if (default_arg_node = extract_hash_pair(second_arg_node, 'default'))
@@ -95,6 +96,7 @@ module I18n::Tasks::Scanners
95
96
  def extract_hash_pair(node, key)
96
97
  node.children.detect do |child|
97
98
  next unless child.type == :pair
99
+
98
100
  key_node = child.children[0]
99
101
  %i[sym str].include?(key_node.type) && key_node.children[0].to_s == key
100
102
  end
@@ -115,15 +117,15 @@ module I18n::Tasks::Scanners
115
117
  def extract_string(node, array_join_with: nil, array_flatten: false, array_reject_blank: false)
116
118
  if %i[sym str int].include?(node.type)
117
119
  node.children[0].to_s
118
- elsif %i[true false].include?(node.type) # rubocop:disable Lint/BooleanSymbol
120
+ elsif %i[true false].include?(node.type)
119
121
  node.type.to_s
120
122
  elsif node.type == :nil
121
123
  ''
122
124
  elsif node.type == :array && array_join_with
123
125
  extract_array_as_string(
124
126
  node,
125
- array_join_with: array_join_with,
126
- array_flatten: array_flatten,
127
+ array_join_with: array_join_with,
128
+ array_flatten: array_flatten,
127
129
  array_reject_blank: array_reject_blank
128
130
  ).tap do |str|
129
131
  # `nil` is returned when a dynamic value is encountered in strict mode. Propagate:
@@ -149,11 +151,12 @@ module I18n::Tasks::Scanners
149
151
  # @return [String, nil] `nil` is returned only when a dynamic value is encountered in strict mode.
150
152
  def extract_array_as_string(node, array_join_with:, array_flatten: false, array_reject_blank: false)
151
153
  children_strings = node.children.map do |child|
152
- if %i[sym str int true false].include?(child.type) # rubocop:disable Lint/BooleanSymbol
154
+ if %i[sym str int true false].include?(child.type)
153
155
  extract_string child
154
156
  else
155
157
  # ignore dynamic argument in strict mode
156
158
  return nil if config[:strict]
159
+
157
160
  if %i[dsym dstr].include?(child.type) || (child.type == :array && array_flatten)
158
161
  extract_string(child, array_join_with: array_join_with)
159
162
  else
@@ -180,12 +183,12 @@ module I18n::Tasks::Scanners
180
183
  # @return [Results::Occurrence]
181
184
  def range_to_occurrence(raw_key, range, default_arg: nil)
182
185
  Results::Occurrence.new(
183
- path: range.source_buffer.name,
184
- pos: range.begin_pos,
185
- line_num: range.line,
186
- line_pos: range.column,
187
- line: range.source_line,
188
- raw_key: raw_key,
186
+ path: range.source_buffer.name,
187
+ pos: range.begin_pos,
188
+ line_num: range.line,
189
+ line_pos: range.column,
190
+ line: range.source_line,
191
+ raw_key: raw_key,
189
192
  default_arg: default_arg
190
193
  )
191
194
  end
@@ -203,4 +206,4 @@ module I18n::Tasks::Scanners
203
206
  end
204
207
  end
205
208
  end
206
- # rubocop:enable Metrics/AbcSize,Metrics/BlockNesting,Metrics/CyclomaticComplexity,Metrics/PerceivedComplexity
209
+ # rubocop:enable Metrics/AbcSize,Metrics/BlockNesting,Metrics/PerceivedComplexity
@@ -2,7 +2,7 @@
2
2
 
3
3
  module I18n::Tasks::Scanners
4
4
  module RubyKeyLiterals
5
- LITERAL_RE = /:?".+?"|:?'.+?'|:\w+/
5
+ LITERAL_RE = /:?".+?"|:?'.+?'|:\w+/.freeze
6
6
 
7
7
  # Match literals:
8
8
  # * String: '', "#{}"
@@ -20,8 +20,8 @@ module I18n::Tasks::Scanners
20
20
  literal
21
21
  end
22
22
 
23
- VALID_KEY_CHARS = /(?:[[:word:]]|[-.?!:;À-ž])/
24
- VALID_KEY_RE = /^#{VALID_KEY_CHARS}+$/
23
+ VALID_KEY_CHARS = /(?:[[:word:]]|[-.?!:;À-ž])/.freeze
24
+ VALID_KEY_RE = /^#{VALID_KEY_CHARS}+$/.freeze
25
25
 
26
26
  def valid_key?(key)
27
27
  key =~ VALID_KEY_RE && !key.end_with?('.')
@@ -9,6 +9,7 @@ module I18n::Tasks::Scanners
9
9
  class ScannerMultiplexer < Scanner
10
10
  # @param scanners [Array<Scanner>]
11
11
  def initialize(scanners:)
12
+ super()
12
13
  @scanners = scanners
13
14
  end
14
15
 
@@ -25,6 +26,7 @@ module I18n::Tasks::Scanners
25
26
  # @return [Array<Array<Results::KeyOccurrences>>]
26
27
  def collect_results
27
28
  return [@scanners[0].keys] if @scanners.length == 1
29
+
28
30
  Array.new(@scanners.length).tap do |results|
29
31
  results_mutex = Mutex.new
30
32
  @scanners.map.with_index do |scanner, i|
@@ -15,6 +15,7 @@ module I18n
15
15
  parts = []
16
16
  pos = 0
17
17
  return [key] if max == 1
18
+
18
19
  key_parts(key) do |part|
19
20
  parts << part
20
21
  pos += part.length + 1
@@ -36,6 +37,7 @@ module I18n
36
37
  # dots inside braces or parenthesis are not split on
37
38
  def key_parts(key, &block)
38
39
  return enum_for(:key_parts, key) unless block
40
+
39
41
  nesting = PARENS
40
42
  counts = PARENS_ZEROS # dup'd later if key contains parenthesis
41
43
  delim = '.'
@@ -6,6 +6,7 @@ module I18n::Tasks
6
6
 
7
7
  def interpolate_soft(s, t = {})
8
8
  return s unless s
9
+
9
10
  t.each do |k, v|
10
11
  pat = "%{#{k}}"
11
12
  s = s.gsub pat, v.to_s if s.include?(pat)
@@ -1,8 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'i18n/tasks/translators/deepl_translator.rb'
4
- require 'i18n/tasks/translators/google_translator.rb'
5
- require 'i18n/tasks/translators/yandex_translator.rb'
3
+ require 'i18n/tasks/translators/deepl_translator'
4
+ require 'i18n/tasks/translators/google_translator'
5
+ require 'i18n/tasks/translators/yandex_translator'
6
6
 
7
7
  module I18n::Tasks
8
8
  module Translation
@@ -24,6 +24,7 @@ module I18n::Tasks
24
24
  # @return [Array<[String, Object]>] translated list
25
25
  def translate_pairs(list, opts)
26
26
  return [] if list.empty?
27
+
27
28
  opts = opts.dup
28
29
  key_pos = list.each_with_index.inject({}) { |idx, ((k, _v), i)| idx.update(k => i) }
29
30
  # copy reference keys as is, instead of translating
@@ -92,8 +93,8 @@ module I18n::Tasks
92
93
  end
93
94
  end
94
95
 
95
- INTERPOLATION_KEY_RE = /%\{[^}]+}/
96
- UNTRANSLATABLE_STRING = 'zxzxzx'
96
+ INTERPOLATION_KEY_RE = /%\{[^}]+}/.freeze
97
+ UNTRANSLATABLE_STRING = 'X__'
97
98
 
98
99
  # @param [String] value
99
100
  # @return [String] 'hello, %{name}' => 'hello, <round-trippable string>'
@@ -110,6 +111,7 @@ module I18n::Tasks
110
111
  # @return [String] 'hello, <round-trippable string>' => 'hello, %{name}'
111
112
  def restore_interpolations(untranslated, translated)
112
113
  return translated if untranslated !~ INTERPOLATION_KEY_RE
114
+
113
115
  values = untranslated.scan(INTERPOLATION_KEY_RE)
114
116
  translated.gsub(/#{Regexp.escape(UNTRANSLATABLE_STRING)}\d+/i) do |m|
115
117
  values[m[UNTRANSLATABLE_STRING.length..-1].to_i]
@@ -17,7 +17,12 @@ module I18n::Tasks::Translators
17
17
  protected
18
18
 
19
19
  def translate_values(list, from:, to:, **options)
20
- DeepL.translate(list, to_deepl_compatible_locale(from), to_deepl_compatible_locale(to), options).map(&:text)
20
+ result = DeepL.translate(list, to_deepl_compatible_locale(from), to_deepl_compatible_locale(to), options)
21
+ if result.is_a?(DeepL::Resources::Text)
22
+ [result.text]
23
+ else
24
+ result.map(&:text)
25
+ end
21
26
  end
22
27
 
23
28
  def options_for_translate_values(**options)
@@ -43,7 +48,8 @@ module I18n::Tasks::Translators
43
48
  # @return [String] 'hello, <i18n>%{name}</i18n>' => 'hello, %{name}'
44
49
  def restore_interpolations(untranslated, translated)
45
50
  return translated if untranslated !~ INTERPOLATION_KEY_RE
46
- translated.gsub(%r{<\/?i18n>}, '')
51
+
52
+ translated.gsub(%r{</?i18n>}, '')
47
53
  rescue StandardError => e
48
54
  raise_interpolation_error(untranslated, translated, e)
49
55
  end
@@ -61,8 +67,15 @@ module I18n::Tasks::Translators
61
67
 
62
68
  def configure_api_key!
63
69
  api_key = @i18n_tasks.translation_config[:deepl_api_key]
70
+ host = @i18n_tasks.translation_config[:deepl_host]
71
+ version = @i18n_tasks.translation_config[:deepl_version]
64
72
  fail ::I18n::Tasks::CommandError, I18n.t('i18n_tasks.deepl_translate.errors.no_api_key') if api_key.blank?
65
- DeepL.configure { |config| config.auth_key = api_key }
73
+
74
+ DeepL.configure { |config|
75
+ config.auth_key = api_key
76
+ config.host = host unless host.blank?
77
+ config.version = version unless version.blank?
78
+ }
66
79
  end
67
80
  end
68
81
  end
@@ -46,6 +46,7 @@ module I18n::Tasks::Translators
46
46
  # Convert 'es-ES' to 'es'
47
47
  def to_google_translate_compatible_locale(locale)
48
48
  return locale unless locale.include?('-') && !SUPPORTED_LOCALES_WITH_REGION.include?(locale)
49
+
49
50
  locale.split('-', 2).first
50
51
  end
51
52
 
@@ -60,6 +61,7 @@ module I18n::Tasks::Translators
60
61
  key ||= translation_config[:api_key]
61
62
  end
62
63
  fail ::I18n::Tasks::CommandError, I18n.t('i18n_tasks.google_translate.errors.no_api_key') if key.blank?
64
+
63
65
  key
64
66
  end
65
67
  end
@@ -43,6 +43,7 @@ module I18n::Tasks::Translators
43
43
  # Convert 'es-ES' to 'es'
44
44
  def to_yandex_compatible_locale(locale)
45
45
  return locale unless locale.include?('-')
46
+
46
47
  locale.split('-', 2).first
47
48
  end
48
49
 
@@ -54,6 +55,7 @@ module I18n::Tasks::Translators
54
55
  @api_key ||= begin
55
56
  key = @i18n_tasks.translation_config[:yandex_api_key]
56
57
  fail ::I18n::Tasks::CommandError, I18n.t('i18n_tasks.yandex_translate.errors.no_api_key') if key.blank?
58
+
57
59
  key
58
60
  end
59
61
  end
@@ -16,16 +16,18 @@ module I18n::Tasks
16
16
  module UsedKeys # rubocop:disable Metrics/ModuleLength
17
17
  SEARCH_DEFAULTS = {
18
18
  paths: %w[app/].freeze,
19
+ relative_exclude_method_name_paths: [],
19
20
  relative_roots: %w[app/controllers app/helpers app/mailers app/presenters app/views].freeze,
20
21
  scanners: [
21
- ['::I18n::Tasks::Scanners::RubyAstScanner', only: %w[*.rb]],
22
- ['::I18n::Tasks::Scanners::PatternWithScopeScanner', exclude: %w[*.rb]]
22
+ ['::I18n::Tasks::Scanners::RubyAstScanner', { only: %w[*.rb] }],
23
+ ['::I18n::Tasks::Scanners::PatternWithScopeScanner', { exclude: %w[*.rb] }]
23
24
  ],
24
25
  strict: true
25
26
  }.freeze
26
27
 
27
- ALWAYS_EXCLUDE = %w[*.jpg *.png *.gif *.svg *.ico *.eot *.otf *.ttf *.woff *.woff2 *.pdf *.css *.sass *.scss *.less
28
- *.yml *.json *.zip *.tar.gz *.swf *.flv *.mp3 *.wav *.flac *.webm *.mp4 *.ogg *.opus].freeze
28
+ ALWAYS_EXCLUDE = %w[*.jpg *.jpeg *.png *.gif *.svg *.ico *.eot *.otf *.ttf *.woff *.woff2 *.pdf *.css *.sass *.scss *.less
29
+ *.yml *.json *.zip *.tar.gz *.swf *.flv *.mp3 *.wav *.flac *.webm *.mp4 *.ogg *.opus
30
+ *.webp].freeze
29
31
 
30
32
  # Find all keys in the source and return a forest with the keys in absolute form and their occurrences.
31
33
  #
@@ -55,8 +57,8 @@ module I18n::Tasks
55
57
  keys = keys.select { |k| k.key =~ key_filter_re }
56
58
  end
57
59
  Data::Tree::Node.new(
58
- key: 'used',
59
- data: { key_filter: key_filter },
60
+ key: 'used',
61
+ data: { key_filter: key_filter },
60
62
  children: Data::Tree::Siblings.from_key_occurrences(keys)
61
63
  ).to_siblings
62
64
  end
@@ -72,10 +74,11 @@ module I18n::Tasks
72
74
  if args && args[:strict]
73
75
  fail CommandError, 'the strict option is global and cannot be applied on the scanner level'
74
76
  end
77
+
75
78
  ActiveSupport::Inflector.constantize(class_name).new(
76
- config: merge_scanner_configs(shared_options, args || {}),
79
+ config: merge_scanner_configs(shared_options, args || {}),
77
80
  file_finder_provider: caching_file_finder_provider,
78
- file_reader: caching_file_reader
81
+ file_reader: caching_file_reader
79
82
  )
80
83
  end.tap { |scanners| log_verbose { scanners.map { |s| " #{s.class.name} #{s.config.inspect}" } * "\n" } }
81
84
  )
@@ -103,7 +106,7 @@ module I18n::Tasks
103
106
 
104
107
  def merge_scanner_configs(a, b)
105
108
  a.deep_merge(b).tap do |c|
106
- %i[scanners paths relative_roots].each do |key|
109
+ %i[scanners paths relative_exclude_method_name_paths relative_roots].each do |key|
107
110
  c[key] = a[key] if b[key].blank?
108
111
  end
109
112
  %i[exclude].each do |key|
@@ -123,7 +126,7 @@ module I18n::Tasks
123
126
 
124
127
  # @return [Boolean] whether the key is potentially used in a code expression such as `t("category.#{category_key}")`
125
128
  def used_in_expr?(key)
126
- !!(key =~ expr_key_re) # rubocop:disable Style/DoubleNegation
129
+ !!(key =~ expr_key_re)
127
130
  end
128
131
 
129
132
  private
@@ -139,12 +142,13 @@ module I18n::Tasks
139
142
  def expr_key_re(replacement: ':')
140
143
  @expr_key_re ||= begin
141
144
  # disallow patterns with no keys
142
- ignore_pattern_re = /\A[\.#{replacement}]*\z/
145
+ ignore_pattern_re = /\A[.#{replacement}]*\z/
143
146
  patterns = used_in_source_tree(strict: false).key_names.select do |k|
144
147
  k.end_with?('.') || k =~ /\#{/
145
148
  end.map do |k|
146
149
  pattern = "#{replace_key_exp(k, replacement)}#{replacement if k.end_with?('.')}"
147
150
  next if pattern =~ ignore_pattern_re
151
+
148
152
  pattern
149
153
  end.compact
150
154
  compile_key_pattern "{#{patterns * ','}}"
@@ -160,10 +164,11 @@ module I18n::Tasks
160
164
  braces = []
161
165
  result = []
162
166
  while (match_until = scanner.scan_until(/(?:#?\{|})/))
163
- if scanner.matched == '#{'
167
+ case scanner.matched
168
+ when '#{'
164
169
  braces << scanner.matched
165
170
  result << match_until[0..-3] if braces.length == 1
166
- elsif scanner.matched == '}'
171
+ when '}'
167
172
  prev_brace = braces.pop
168
173
  result << replacement if braces.empty? && prev_brace == '#{'
169
174
  else
@@ -2,6 +2,6 @@
2
2
 
3
3
  module I18n
4
4
  module Tasks
5
- VERSION = '0.9.31'
5
+ VERSION = '0.9.35'
6
6
  end
7
7
  end
@@ -32,7 +32,7 @@ data:
32
32
  # This data is not considered unused and is never written to.
33
33
  external:
34
34
  ## Example (replace %#= with %=):
35
- # - "<%#= %x[bundle show vagrant].chomp %>/templates/locales/%{locale}.yml"
35
+ # - "<%#= %x[bundle info vagrant --path].chomp %>/templates/locales/%{locale}.yml"
36
36
 
37
37
  ## Specify the router (see Readme for details). Valid values: conservative_router, pattern_router, or a custom class.
38
38
  # router: conservative_router
@@ -64,6 +64,13 @@ search:
64
64
  # - app/presenters
65
65
  # - app/views
66
66
 
67
+ ## Directories where method names which should not be part of a relative key resolution.
68
+ # By default, if a relative translation is used inside a method, the name of the method will be considered part of the resolved key.
69
+ # Directories listed here will not consider the name of the method part of the resolved key
70
+ #
71
+ # relative_exclude_method_name_paths:
72
+ # -
73
+
67
74
  ## Files or `File.fnmatch` patterns to exclude from search. Some files are always excluded regardless of this setting:
68
75
  ## %w(*.jpg *.png *.gif *.svg *.ico *.eot *.otf *.ttf *.woff *.woff2 *.pdf *.css *.sass *.scss *.less *.yml *.json)
69
76
  exclude:
@@ -90,6 +97,8 @@ search:
90
97
  # # DeepL Pro Translate
91
98
  # # Get an API key and subscription at https://www.deepl.com/pro to use DeepL Pro
92
99
  # deepl_api_key: "48E92789-57A3-466A-9959-1A1A1A1A1A1A"
100
+ # # deepl_host: "https://api.deepl.com"
101
+ # # deepl_version: "v2"
93
102
 
94
103
  ## Do not consider these keys missing:
95
104
  # ignore_missing:
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.31
4
+ version: 0.9.35
5
5
  platform: ruby
6
6
  authors:
7
7
  - glebm
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-03-10 00:00:00.000000000 Z
11
+ date: 2021-11-06 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -210,14 +210,14 @@ dependencies:
210
210
  requirements:
211
211
  - - "~>"
212
212
  - !ruby/object:Gem::Version
213
- version: 0.53.0
213
+ version: 1.6.1
214
214
  type: :development
215
215
  prerelease: false
216
216
  version_requirements: !ruby/object:Gem::Requirement
217
217
  requirements:
218
218
  - - "~>"
219
219
  - !ruby/object:Gem::Version
220
- version: 0.53.0
220
+ version: 1.6.1
221
221
  - !ruby/object:Gem::Dependency
222
222
  name: simplecov
223
223
  requirement: !ruby/object:Gem::Requirement
@@ -404,16 +404,19 @@ require_paths:
404
404
  - lib
405
405
  required_ruby_version: !ruby/object:Gem::Requirement
406
406
  requirements:
407
- - - "~>"
407
+ - - ">="
408
+ - !ruby/object:Gem::Version
409
+ version: '2.5'
410
+ - - "<"
408
411
  - !ruby/object:Gem::Version
409
- version: '2.3'
412
+ version: '4.0'
410
413
  required_rubygems_version: !ruby/object:Gem::Requirement
411
414
  requirements:
412
415
  - - ">="
413
416
  - !ruby/object:Gem::Version
414
417
  version: '0'
415
418
  requirements: []
416
- rubygems_version: 3.0.3
419
+ rubygems_version: 3.1.2
417
420
  signing_key:
418
421
  specification_version: 4
419
422
  summary: Manage localization and translation with the awesome power of static analysis