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.
- checksums.yaml +4 -4
- data/README.md +65 -38
- data/config/locales/en.yml +11 -1
- data/config/locales/ru.yml +11 -1
- data/i18n-tasks.gemspec +4 -1
- data/lib/i18n/tasks/command/commands/missing.rb +3 -1
- data/lib/i18n/tasks/command/option_parsers/enum.rb +4 -3
- data/lib/i18n/tasks/command/options/locales.rb +12 -3
- data/lib/i18n/tasks/configuration.rb +7 -2
- data/lib/i18n/tasks/scanners/ast_matchers/rails_model_matcher.rb +1 -1
- data/lib/i18n/tasks/scanners/local_ruby_parser.rb +2 -2
- data/lib/i18n/tasks/scanners/pattern_scanner.rb +1 -1
- data/lib/i18n/tasks/scanners/prism_scanner.rb +83 -0
- data/lib/i18n/tasks/scanners/prism_scanners/arguments_visitor.rb +41 -0
- data/lib/i18n/tasks/scanners/prism_scanners/nodes.rb +334 -0
- data/lib/i18n/tasks/scanners/prism_scanners/visitor.rb +273 -0
- data/lib/i18n/tasks/scanners/ruby_ast_scanner.rb +3 -3
- data/lib/i18n/tasks/scanners/ruby_key_literals.rb +1 -1
- data/lib/i18n/tasks/scanners/ruby_parser_factory.rb +27 -0
- data/lib/i18n/tasks/translation.rb +4 -1
- data/lib/i18n/tasks/translators/base_translator.rb +5 -1
- data/lib/i18n/tasks/translators/deepl_translator.rb +5 -0
- data/lib/i18n/tasks/translators/google_translator.rb +12 -4
- data/lib/i18n/tasks/translators/openai_translator.rb +34 -20
- data/lib/i18n/tasks/translators/watsonx_translator.rb +155 -0
- data/lib/i18n/tasks/translators/yandex_translator.rb +5 -1
- data/lib/i18n/tasks/used_keys.rb +3 -2
- data/lib/i18n/tasks/version.rb +1 -1
- data/lib/i18n/tasks.rb +1 -0
- data/templates/config/i18n-tasks.yml +2 -2
- metadata +31 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 6c2d36ee3f03672492a67bc2b98344a65051392dfaf0891aee1cbe1c01126884
|
4
|
+
data.tar.gz: 1e8ff7f7b02d804be0c4d077424cec4dfbb5881b3bcada145b1c2878e3913e2b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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.
|
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
|
-
###
|
87
|
+
### Translate Missing Keys
|
88
88
|
|
89
|
-
Translate missing
|
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
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
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.
|
data/config/locales/en.yml
CHANGED
@@ -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
|
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: >-
|
data/config/locales/ru.yml
CHANGED
@@ -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: Движок перевода
|
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
|
-
|
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
|
19
|
+
return @valid.first if value.blank? && !@allow_blank
|
19
20
|
|
20
21
|
if @valid.include?(value)
|
21
22
|
value
|
22
|
-
|
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:
|
40
|
-
|
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
|
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 '
|
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 =
|
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
|