i18n-tasks 1.0.14 → 1.0.15

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 (31) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +65 -38
  3. data/config/locales/en.yml +11 -1
  4. data/config/locales/ru.yml +11 -1
  5. data/i18n-tasks.gemspec +4 -1
  6. data/lib/i18n/tasks/command/commands/missing.rb +3 -1
  7. data/lib/i18n/tasks/command/option_parsers/enum.rb +4 -3
  8. data/lib/i18n/tasks/command/options/locales.rb +12 -3
  9. data/lib/i18n/tasks/configuration.rb +7 -2
  10. data/lib/i18n/tasks/scanners/ast_matchers/rails_model_matcher.rb +1 -1
  11. data/lib/i18n/tasks/scanners/local_ruby_parser.rb +2 -2
  12. data/lib/i18n/tasks/scanners/pattern_scanner.rb +1 -1
  13. data/lib/i18n/tasks/scanners/prism_scanner.rb +83 -0
  14. data/lib/i18n/tasks/scanners/prism_scanners/arguments_visitor.rb +41 -0
  15. data/lib/i18n/tasks/scanners/prism_scanners/nodes.rb +334 -0
  16. data/lib/i18n/tasks/scanners/prism_scanners/visitor.rb +273 -0
  17. data/lib/i18n/tasks/scanners/ruby_ast_scanner.rb +3 -3
  18. data/lib/i18n/tasks/scanners/ruby_key_literals.rb +1 -1
  19. data/lib/i18n/tasks/scanners/ruby_parser_factory.rb +27 -0
  20. data/lib/i18n/tasks/translation.rb +4 -1
  21. data/lib/i18n/tasks/translators/base_translator.rb +5 -1
  22. data/lib/i18n/tasks/translators/deepl_translator.rb +5 -0
  23. data/lib/i18n/tasks/translators/google_translator.rb +12 -4
  24. data/lib/i18n/tasks/translators/openai_translator.rb +34 -20
  25. data/lib/i18n/tasks/translators/watsonx_translator.rb +155 -0
  26. data/lib/i18n/tasks/translators/yandex_translator.rb +5 -1
  27. data/lib/i18n/tasks/used_keys.rb +3 -2
  28. data/lib/i18n/tasks/version.rb +1 -1
  29. data/lib/i18n/tasks.rb +1 -0
  30. data/templates/config/i18n-tasks.yml +2 -2
  31. metadata +31 -5
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: bd61ceb254dc1c44768bd40672b3f31635c9d42fb237950cbdfe64fef642a850
4
- data.tar.gz: ae6981dd739636ddb5f1bae1ca9e5cf05ba991fd1345a5488e1cfa8561833877
3
+ metadata.gz: 6c2d36ee3f03672492a67bc2b98344a65051392dfaf0891aee1cbe1c01126884
4
+ data.tar.gz: 1e8ff7f7b02d804be0c4d077424cec4dfbb5881b3bcada145b1c2878e3913e2b
5
5
  SHA512:
6
- metadata.gz: 1312910139411cc4695bea0f0de8624d49958efac33c8c7b7bd99fb3f351d408d0a6980789ec81a43452bc29a15292f6de9719ace0e3b902866d63c02f5cab79
7
- data.tar.gz: e782f1bc1f1ad68cc33583d014c879b3d121ba2e0990db8cc109f864aeb0e98f0a97030300294e31e9041899b131b2b677fe6917f705d09bda6bf07aed588b42
6
+ metadata.gz: 1955ea4adbf216f1e4014ea4b5f3873d45a14304a7c5d36b1692f5bd6924f358dd2bd90154d3a857678b50860ee1c248a2d914eef98af54062f8f03aced8a0ce
7
+ data.tar.gz: 9a3e08a0a8ab27242701ea48dc68be4635e7f553d647e7f27d1449be51a014f0b0fa8d35bccd67ff2402ad9d25400f4463d702bac9cf5e285786211d4c451635
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.14', group: :development
27
+ gem 'i18n-tasks', '~> 1.0.15', group: :development
28
28
  ```
29
29
 
30
30
  Copy the default [configuration file](#configuration):
@@ -84,49 +84,23 @@ Usage: i18n-tasks add-missing [options] [locale ...]
84
84
  -h, --help Display this help message.
85
85
  ```
86
86
 
87
- ### Google Translate missing keys
87
+ ### Translate Missing Keys
88
88
 
89
- Translate missing values with Google Translate ([more below on the API key](#google-translation-config)).
89
+ Translate missing keys using a backend service of your choice.
90
90
 
91
91
  ```console
92
92
  $ i18n-tasks translate-missing
93
93
 
94
- # accepts from and locales options:
95
- $ i18n-tasks translate-missing --from=base es fr
94
+ # accepts backend, from and locales options
95
+ $ i18n-tasks translate-missing --from=base es fr --backend=google
96
96
  ```
97
97
 
98
- ### DeepL Pro Translate missing keys
99
-
100
- Translate missing values with DeepL Pro Translate ([more below on the API key](#deepl-translation-config)).
101
-
102
- ```console
103
- $ i18n-tasks translate-missing --backend=deepl
104
-
105
- # accepts from and locales options:
106
- $ i18n-tasks translate-missing --backend=deepl --from=en fr nl
107
- ```
108
-
109
- ### Yandex Translate missing keys
110
-
111
- Translate missing values with Yandex Translate ([more below on the API key](#yandex-translation-config)).
112
-
113
- ```console
114
- $ i18n-tasks translate-missing --backend=yandex
115
-
116
- # accepts from and locales options:
117
- $ i18n-tasks translate-missing --from=en es fr
118
- ```
119
-
120
- ### OpenAI Translate missing keys
121
-
122
- Translate missing values with OpenAI ([more below on the API key](#openai-translation-config)).
123
-
124
- ```console
125
- $ i18n-tasks translate-missing --backend=openai
126
-
127
- # accepts from and locales options:
128
- $ i18n-tasks translate-missing --from=en es fr
129
- ```
98
+ Available backends:
99
+ - `google` - [Google Translate](#google-translation-config)
100
+ - `deepl` - [DeepL Pro](#deepl-translation-config)
101
+ - `yandex` - [Yandex Translate](#yandex-translation-config)
102
+ - `openai` - [OpenAI](#openai-translation-config)
103
+ - `watsonx` - [watsonx](#watsonx-translation-config)
130
104
 
131
105
  ### Find usages
132
106
 
@@ -435,6 +409,7 @@ Put the key in `GOOGLE_TRANSLATE_API_KEY` environment variable or in the config
435
409
  ```yaml
436
410
  # config/i18n-tasks.yml
437
411
  translation:
412
+ backend: google
438
413
  google_translate_api_key: <Google Translate API key>
439
414
  ```
440
415
 
@@ -447,11 +422,12 @@ GOOGLE_TRANSLATE_API_KEY=<Google Translate API key>
447
422
  <a name="deepl-translation-config"></a>
448
423
  ### DeepL Pro Translate
449
424
 
450
- `i18n-tasks translate-missing` requires a DeepL Pro API key, get it at [DeepL](https://www.deepl.com/pro).
425
+ `i18n-tasks translate-missing` requires a DeepL Pro API key, get it at [DeepL](https://www.deepl.com/pro). You can specify alias locales if you only use the simple locales internally.
451
426
 
452
427
  ```yaml
453
428
  # config/i18n-tasks.yml
454
429
  translation:
430
+ backend: deepl
455
431
  deepl_api_key: <DeepL Pro API key>
456
432
  deepl_host: <optional>
457
433
  deepl_version: <optional>
@@ -460,6 +436,9 @@ translation:
460
436
  - 2c6415be-1852-4f54-9e1b-d800463496b4
461
437
  deepl_options:
462
438
  formality: prefer_less
439
+ deepl_locale_aliases:
440
+ en: en-us
441
+ pt: pt-br
463
442
  ```
464
443
 
465
444
  or via environment variables:
@@ -478,6 +457,7 @@ DEEPL_VERSION=<optional>
478
457
  ```yaml
479
458
  # config/i18n-tasks.yml
480
459
  translation:
460
+ backend: yandex
481
461
  yandex_api_key: <Yandex API key>
482
462
  ```
483
463
 
@@ -495,6 +475,7 @@ YANDEX_API_KEY=<Yandex API key>
495
475
  ```yaml
496
476
  # config/i18n-tasks.yml
497
477
  translation:
478
+ backend: openai
498
479
  openai_api_key: <OpenAI API key>
499
480
  openai_model: <optional>
500
481
  ```
@@ -506,6 +487,52 @@ OPENAI_API_KEY=<OpenAI API key>
506
487
  OPENAI_MODEL=<optional>
507
488
  ```
508
489
 
490
+ <a name="watsonx-translation-config"></a>
491
+ ### watsonx Translate
492
+
493
+ `i18n-tasks translate-missing` requires a watsonx project and api key, get it at [IBM watsonx](https://www.ibm.com/watsonx/).
494
+
495
+ ```yaml
496
+ # config/i18n-tasks.yml
497
+ translation:
498
+ backend: watsonx
499
+ watsonx_api_key: <watsonx API key>
500
+ watsonx_project_id: <watsonx project id>
501
+ watsonx_model: <optional>
502
+ ```
503
+
504
+ or via environment variable:
505
+
506
+ ```bash
507
+ WATSONX_API_KEY=<watsonx API key>
508
+ WATSONX_PROJECT_ID=<watsonx project id>
509
+ WATSONX_MODEL=<optional>
510
+ ```
511
+
512
+ ### Contextual Rails Parser
513
+
514
+ There is an experimental feature to parse Rails with more context. `i18n-tasks` will support:
515
+ - Translations called in `before_actions`
516
+ - Translations called in nested methods
517
+ - `Model.human_attribute_name` calls
518
+ - `Model.model_name.human` calls
519
+
520
+ Enabled it by adding the scanner in your `config/i18n-tasks.yml`:
521
+
522
+ ```ruby
523
+ <% I18n::Tasks.add_scanner(
524
+ 'I18n::Tasks::Scanners::PrismScanner',
525
+ only: %w(*.rb)
526
+ ) %>
527
+ ```
528
+
529
+ To only enable Ruby-scanning and not any Rails support, please add config under the `search` section:
530
+
531
+ ```yaml
532
+ search:
533
+ prism_visitor: "ruby" # default "rails"
534
+ ```
535
+
509
536
  ## Interactive console
510
537
 
511
538
  `i18n-tasks irb` starts an IRB session in i18n-tasks context. Type `guide` for more information.
@@ -26,7 +26,7 @@ en:
26
26
  strict: >-
27
27
  Avoid inferring dynamic key usages such as t("cats.#{cat}.name"). Takes precedence over
28
28
  the config setting if set.
29
- translation_backend: Translation backend (google or deepl)
29
+ translation_backend: Translation backend [google, deepl, yandex, openai])
30
30
  value: >-
31
31
  Value. Interpolates: %%{value}, %%{human_key}, %%{key}, %%{default}, %%{value_or_human_key},
32
32
  %%{value_or_default_or_human_key}
@@ -69,6 +69,7 @@ en:
69
69
  enum_opt:
70
70
  invalid: "%{invalid} is not one of: %{valid}."
71
71
  errors:
72
+ invalid_backend: 'Invalid backend: %{invalid}. Must be one of %{valid}.'
72
73
  invalid_format: 'invalid format: %{invalid}. valid: %{valid}.'
73
74
  invalid_locale: 'invalid locale: %{invalid}'
74
75
  invalid_missing_type:
@@ -129,6 +130,15 @@ en:
129
130
  none: Every translation is in use.
130
131
  usages:
131
132
  none: No key usages found.
133
+ watsonx_translate:
134
+ errors:
135
+ no_api_key: >-
136
+ Set watsonx API key via WATSONX_API_KEY environment variable or translation.watsonx_api_key
137
+ in config/i18n-tasks.yml. Get the key at https://www.ibm.com/products/watsonx-ai.
138
+ no_project_id: >-
139
+ Set watsonx Project ID via WATSONX_PROJECT_ID environment variable or translation.watsonx_api_key
140
+ in config/i18n-tasks.yml. Get the key at https://www.ibm.com/products/watsonx-ai.
141
+ no_results: watsonx returned no results.
132
142
  yandex_translate:
133
143
  errors:
134
144
  no_api_key: >-
@@ -23,7 +23,7 @@ ru:
23
23
  out_format: 'Формат вывода: %{valid_text}.'
24
24
  pattern_router: 'Использовать pattern_router: ключи распределятся по файлам согласно data.write'
25
25
  strict: Не угадывать динамические использования ключей, например `t("category.#{category.key}")`
26
- translation_backend: Движок перевода (google или deepl)
26
+ translation_backend: Движок перевода [google, deepl, yandex, openai]
27
27
  value: >-
28
28
  Значение, интерполируется с %%{value}, %%{human_key}, %%{key}, %%{default}, %%{value_or_human_key},
29
29
  %%{value_or_default_or_human_key}
@@ -66,6 +66,7 @@ ru:
66
66
  enum_opt:
67
67
  invalid: "%{invalid} не является одним из: %{valid}."
68
68
  errors:
69
+ invalid_backend: 'Недопустимый источник данных: %{invalid}. Должен быть одним из %{valid}.'
69
70
  invalid_format: 'Неизвестный формат %{invalid}. Форматы: %{valid}.'
70
71
  invalid_locale: Неверный язык %{invalid}
71
72
  invalid_missing_type:
@@ -130,6 +131,15 @@ ru:
130
131
  none: Все переводы используются.
131
132
  usages:
132
133
  none: Не найдено использований.
134
+ watsonx_translate:
135
+ errors:
136
+ no_api_key: >-
137
+ Установите ключ API watsonx через переменную среды WATSONX_API_KEY или translation.watsonx_api_key
138
+ в config/i18n-tasks.yml. Получите ключ на https://www.ibm.com/products/watsonx-ai.
139
+ no_project_id: >-
140
+ Установите идентификатор проекта watsonx через переменную среды WATSONX_PROJECT_ID или translation.watsonx_api_key
141
+ в config/i18n-tasks.yml. Получите ключ на https://www.ibm.com/products/watsonx-ai.
142
+ no_results: watsonx не вернул результатов.
133
143
  yandex_translate:
134
144
  errors:
135
145
  no_api_key: |-
data/i18n-tasks.gemspec CHANGED
@@ -27,8 +27,10 @@ Gem::Specification.new do |s|
27
27
  TEXT
28
28
  s.homepage = 'https://github.com/glebm/i18n-tasks'
29
29
  s.metadata = {
30
+ 'changelog_uri' => 'https://github.com/glebm/i18n-tasks/blob/main/CHANGES.md',
30
31
  'issue_tracker' => 'https://github.com/glebm/i18n-tasks',
31
- 'rubygems_mfa_required' => 'true'
32
+ 'rubygems_mfa_required' => 'true',
33
+ 'source_code_uri' => 'https://github.com/glebm/i18n-tasks'
32
34
  }
33
35
  s.required_ruby_version = '>= 2.6', '< 4.0'
34
36
 
@@ -45,6 +47,7 @@ Gem::Specification.new do |s|
45
47
  s.add_dependency 'parser', '>= 3.2.2.1'
46
48
  s.add_dependency 'rails-i18n'
47
49
  s.add_dependency 'rainbow', '>= 2.2.2', '< 4.0'
50
+ s.add_dependency 'ruby-progressbar', '~> 1.8', '>= 1.8.1'
48
51
  s.add_dependency 'terminal-table', '>= 1.5.1'
49
52
  s.add_development_dependency 'bundler', '~> 2.0', '>= 2.0.1'
50
53
  s.add_development_dependency 'overcommit', '~> 0.58.0'
@@ -48,7 +48,9 @@ module I18n::Tasks
48
48
  pattern_re = i18n.compile_key_pattern(opt[:pattern])
49
49
  missing.select_keys! { |full_key, _node| full_key =~ pattern_re }
50
50
  end
51
- translated = i18n.translate_forest missing, from: opt[:from], backend: opt[:backend].to_sym
51
+
52
+ backend = opt[:backend].presence || i18n.translation_config[:backend]
53
+ translated = i18n.translate_forest missing, from: opt[:from], backend: backend.to_sym
52
54
  i18n.data.merge! translated
53
55
  log_stderr t('i18n_tasks.translate_missing.translated', count: translated.leaves.count)
54
56
  print_forest translated, opt
@@ -9,17 +9,18 @@ module I18n::Tasks
9
9
  I18n.t('i18n_tasks.cmd.enum_opt.invalid', invalid: invalid, valid: valid * ', ')
10
10
  end
11
11
 
12
- def initialize(valid, error_message = DEFAULT_ERROR)
12
+ def initialize(valid, error_message = DEFAULT_ERROR, allow_blank: false)
13
13
  @valid = valid.map(&:to_s)
14
14
  @error_message = error_message
15
+ @allow_blank = allow_blank
15
16
  end
16
17
 
17
18
  def call(value, *)
18
- return @valid.first unless value.present?
19
+ return @valid.first if value.blank? && !@allow_blank
19
20
 
20
21
  if @valid.include?(value)
21
22
  value
22
- else
23
+ elsif value.present? || !@allow_blank
23
24
  fail CommandError, @error_message.call(value, @valid)
24
25
  end
25
26
  end
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'i18n/tasks/command/option_parsers/locale'
4
+ require 'i18n/tasks/command/option_parsers/enum'
4
5
 
5
6
  module I18n::Tasks
6
7
  module Command
@@ -31,13 +32,21 @@ module I18n::Tasks
31
32
  parser: OptionParsers::Locale::Parser,
32
33
  default: 'base'
33
34
 
34
- TRANSLATION_BACKENDS = %w[google deepl].freeze
35
+ TRANSLATION_BACKENDS = %w[google deepl yandex openai watsonx].freeze
35
36
  arg :translation_backend,
36
37
  '-b',
37
38
  '--backend BACKEND',
38
39
  t('i18n_tasks.cmd.args.desc.translation_backend'),
39
- parser: OptionParsers::Locale::Parser,
40
- default: TRANSLATION_BACKENDS[0]
40
+ parser:
41
+ OptionParsers::Enum::Parser.new(
42
+ TRANSLATION_BACKENDS,
43
+ proc do |value, valid|
44
+ if value.present?
45
+ I18n.t('i18n_tasks.cmd.errors.invalid_backend', invalid: value&.strip, valid: valid * ', ')
46
+ end
47
+ end,
48
+ allow_blank: true
49
+ )
41
50
  end
42
51
  end
43
52
  end
@@ -5,7 +5,8 @@ module I18n::Tasks::Configuration # rubocop:disable Metrics/ModuleLength
5
5
  base_locale: 'en',
6
6
  internal_locale: 'en',
7
7
  search: ::I18n::Tasks::UsedKeys::SEARCH_DEFAULTS,
8
- data: ::I18n::Tasks::Data::DATA_DEFAULTS
8
+ data: ::I18n::Tasks::Data::DATA_DEFAULTS,
9
+ translation_backend: :google
9
10
  }.freeze
10
11
 
11
12
  # i18n-tasks config (defaults + config/i18n-tasks.yml)
@@ -59,15 +60,19 @@ module I18n::Tasks::Configuration # rubocop:disable Metrics/ModuleLength
59
60
 
60
61
  # translation config
61
62
  # @return [Hash{String => String,Hash,Array}]
62
- def translation_config
63
+ def translation_config # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
63
64
  @config_sections[:translation] ||= begin
64
65
  conf = (config[:translation] || {}).with_indifferent_access
66
+ conf[:backend] ||= DEFAULTS[:translation_backend]
65
67
  conf[:google_translate_api_key] = ENV['GOOGLE_TRANSLATE_API_KEY'] if ENV.key?('GOOGLE_TRANSLATE_API_KEY')
66
68
  conf[:deepl_api_key] = ENV['DEEPL_AUTH_KEY'] if ENV.key?('DEEPL_AUTH_KEY')
67
69
  conf[:deepl_host] = ENV['DEEPL_HOST'] if ENV.key?('DEEPL_HOST')
68
70
  conf[:deepl_version] = ENV['DEEPL_VERSION'] if ENV.key?('DEEPL_VERSION')
69
71
  conf[:openai_api_key] = ENV['OPENAI_API_KEY'] if ENV.key?('OPENAI_API_KEY')
70
72
  conf[:openai_model] = ENV['OPENAI_MODEL'] if ENV.key?('OPENAI_MODEL')
73
+ conf[:watsonx_api_key] = ENV['WATSONX_API_KEY'] if ENV.key?('WATSONX_API_KEY')
74
+ conf[:watsonx_project_id] = ENV['WATSONX_PROJECT_ID'] if ENV.key?('WATSONX_PROJECT_ID')
75
+ conf[:watsonx_model] = ENV['WATSONX_MODEL'] if ENV.key?('WATSONX_MODEL')
71
76
  conf[:yandex_api_key] = ENV['YANDEX_API_KEY'] if ENV.key?('YANDEX_API_KEY')
72
77
  conf
73
78
  end
@@ -16,7 +16,7 @@ module I18n::Tasks::Scanners::AstMatchers
16
16
  receiver = children[0]
17
17
  method_name = children[1]
18
18
 
19
- return unless method_name == :human_attribute_name && receiver.type == :const
19
+ return unless method_name == :human_attribute_name && receiver&.type == :const
20
20
 
21
21
  value = children[2]
22
22
 
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'parser/current'
3
+ require 'i18n/tasks/scanners/ruby_parser_factory'
4
4
 
5
5
  module I18n::Tasks::Scanners
6
6
  class LocalRubyParser
@@ -9,7 +9,7 @@ module I18n::Tasks::Scanners
9
9
  BLOCK_EXPR = /\s*((\s+|\))do|\{)(\s*\|[^|]*\|)?\s*\Z/.freeze
10
10
 
11
11
  def initialize(ignore_blocks: false)
12
- @parser = ::Parser::CurrentRuby.new
12
+ @parser = RubyParserFactory.create_parser
13
13
  @ignore_blocks = ignore_blocks
14
14
  end
15
15
 
@@ -12,7 +12,7 @@ module I18n::Tasks::Scanners
12
12
  include OccurrenceFromPosition
13
13
  include RubyKeyLiterals
14
14
 
15
- TRANSLATE_CALL_RE = /(?<=^|[^\p{L}'\-.]|[^\p{L}'-]I18n\.|I18n\.)t(?:!|ranslate!?)?/.freeze
15
+ TRANSLATE_CALL_RE = /(?<=^|[^\p{L}_'\-.]|[^\p{L}'-]I18n\.|I18n\.)t(?:!|ranslate!?)?/.freeze
16
16
  IGNORE_LINES = {
17
17
  'coffee' => /^\s*#(?!\si18n-tasks-use)/,
18
18
  'erb' => /^\s*<%\s*#(?!\si18n-tasks-use)/,
@@ -0,0 +1,83 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'file_scanner'
4
+ require_relative 'ruby_ast_scanner'
5
+
6
+ module I18n::Tasks::Scanners
7
+ class PrismScanner < FileScanner
8
+ MAGIC_COMMENT_SKIP_PRISM = 'i18n-tasks-skip-prism'
9
+
10
+ def initialize(**args)
11
+ unless VISITOR
12
+ warn(
13
+ 'Please make sure `prism` is available to use this feature. Fallback to Ruby AST Scanner.'
14
+ )
15
+ end
16
+ super
17
+
18
+ @fallback = RubyAstScanner.new(**args)
19
+ end
20
+
21
+ protected
22
+
23
+ # Extract all occurrences of translate calls from the file at the given path.
24
+ #
25
+ # @return [Array<[key, Results::KeyOccurrence]>] each occurrence found in the file
26
+ def scan_file(path)
27
+ return @fallback.send(:scan_file, path) if VISITOR.nil?
28
+
29
+ process_results(path, PARSER.parse_file(path))
30
+ rescue Exception => e # rubocop:disable Lint/RescueException
31
+ raise(
32
+ ::I18n::Tasks::CommandError.new(
33
+ e,
34
+ "Error scanning #{path}: #{e.message}"
35
+ )
36
+ )
37
+ end
38
+
39
+ # Need to have method that can be overridden to be able to test it
40
+ def process_results(path, parse_results)
41
+ parsed = parse_results.value
42
+ comments = parse_results.comments
43
+
44
+ return @fallback.send(:scan_file, path) if skip_prism_comment?(comments)
45
+
46
+ rails = if config[:prism_visitor].blank?
47
+ true
48
+ else
49
+ config[:prism_visitor] != 'ruby'
50
+ end
51
+
52
+ visitor = VISITOR.new(comments: comments, rails: rails)
53
+ parsed.accept(visitor)
54
+
55
+ occurrences = []
56
+ visitor.process.each do |translation_call|
57
+ result = translation_call.occurrences(path)
58
+ occurrences << result if result
59
+ end
60
+
61
+ occurrences
62
+ end
63
+
64
+ def skip_prism_comment?(comments)
65
+ comments.any? do |comment|
66
+ content =
67
+ comment.respond_to?(:slice) ? comment.slice : comment.location.slice
68
+ content.include?(MAGIC_COMMENT_SKIP_PRISM)
69
+ end
70
+ end
71
+
72
+ # This block handles adding a fallback if the `prism` gem is not available.
73
+ begin
74
+ require 'prism'
75
+ require_relative 'prism_scanners/visitor'
76
+ PARSER = Prism
77
+ VISITOR = I18n::Tasks::Scanners::PrismScanners::Visitor
78
+ rescue LoadError
79
+ PARSER = nil
80
+ VISITOR = nil
81
+ end
82
+ end
83
+ end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'prism/visitor'
4
+
5
+ # This class is used to parse the arguments to e.g. a Prism::CallNode and return the values we need
6
+ # for turning them into translations and occurrences.
7
+ # Used in the PrismScanners::Visitor class.
8
+ module I18n::Tasks::Scanners::PrismScanners
9
+ class ArgumentsVisitor < Prism::Visitor
10
+ def visit_keyword_hash_node(node)
11
+ node.child_nodes.each_with_object({}) do |child, hash|
12
+ hash[visit(child.key)] = visit(child.value)
13
+ hash
14
+ end
15
+ end
16
+
17
+ def visit_symbol_node(node)
18
+ node.value
19
+ end
20
+
21
+ def visit_string_node(node)
22
+ node.content
23
+ end
24
+
25
+ def visit_array_node(node)
26
+ node.child_nodes.map { |child| visit(child) }
27
+ end
28
+
29
+ def visit_arguments_node(node)
30
+ node.child_nodes.map { |child| visit(child) }
31
+ end
32
+
33
+ def visit_integer_node(node)
34
+ node.value
35
+ end
36
+
37
+ def visit_lambda_node(node)
38
+ node
39
+ end
40
+ end
41
+ end