i18n-tasks 1.0.13 → 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 (44) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +125 -38
  3. data/config/locales/en.yml +14 -5
  4. data/config/locales/ru.yml +14 -5
  5. data/i18n-tasks.gemspec +4 -2
  6. data/lib/i18n/tasks/command/commands/data.rb +14 -0
  7. data/lib/i18n/tasks/command/commands/missing.rb +3 -1
  8. data/lib/i18n/tasks/command/option_parsers/enum.rb +4 -3
  9. data/lib/i18n/tasks/command/options/common.rb +1 -1
  10. data/lib/i18n/tasks/command/options/locales.rb +12 -3
  11. data/lib/i18n/tasks/configuration.rb +8 -2
  12. data/lib/i18n/tasks/data/file_system_base.rb +5 -0
  13. data/lib/i18n/tasks/data/router/isolating_router.rb +146 -0
  14. data/lib/i18n/tasks/data/tree/siblings.rb +2 -2
  15. data/lib/i18n/tasks/interpolations.rb +1 -1
  16. data/lib/i18n/tasks/key_pattern_matching.rb +4 -4
  17. data/lib/i18n/tasks/reports/terminal.rb +6 -0
  18. data/lib/i18n/tasks/scanners/ast_matchers/default_i18n_subject_matcher.rb +26 -0
  19. data/lib/i18n/tasks/scanners/ast_matchers/rails_model_matcher.rb +1 -1
  20. data/lib/i18n/tasks/scanners/erb_ast_scanner.rb +55 -25
  21. data/lib/i18n/tasks/scanners/local_ruby_parser.rb +2 -2
  22. data/lib/i18n/tasks/scanners/pattern_scanner.rb +1 -1
  23. data/lib/i18n/tasks/scanners/prism_scanner.rb +83 -0
  24. data/lib/i18n/tasks/scanners/prism_scanners/arguments_visitor.rb +41 -0
  25. data/lib/i18n/tasks/scanners/prism_scanners/nodes.rb +334 -0
  26. data/lib/i18n/tasks/scanners/prism_scanners/visitor.rb +273 -0
  27. data/lib/i18n/tasks/scanners/relative_keys.rb +1 -1
  28. data/lib/i18n/tasks/scanners/ruby_ast_scanner.rb +5 -4
  29. data/lib/i18n/tasks/scanners/ruby_key_literals.rb +1 -1
  30. data/lib/i18n/tasks/scanners/ruby_parser_factory.rb +27 -0
  31. data/lib/i18n/tasks/split_key.rb +30 -47
  32. data/lib/i18n/tasks/translation.rb +4 -1
  33. data/lib/i18n/tasks/translators/base_translator.rb +11 -1
  34. data/lib/i18n/tasks/translators/deepl_translator.rb +32 -1
  35. data/lib/i18n/tasks/translators/google_translator.rb +35 -12
  36. data/lib/i18n/tasks/translators/openai_translator.rb +55 -23
  37. data/lib/i18n/tasks/translators/watsonx_translator.rb +155 -0
  38. data/lib/i18n/tasks/translators/yandex_translator.rb +5 -1
  39. data/lib/i18n/tasks/used_keys.rb +1 -0
  40. data/lib/i18n/tasks/version.rb +1 -1
  41. data/lib/i18n/tasks.rb +1 -0
  42. data/templates/config/i18n-tasks.yml +26 -3
  43. metadata +33 -26
  44. data/lib/i18n/tasks/scanners/erb_ast_processor.rb +0 -74
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 12e19e4d7fe61bd20e000b0b6e4db5bdf233443e8e8cf965b62e634bdfa3e5a2
4
- data.tar.gz: 3dab05338d079c4defad590466ab2985faf43245d616f86af4c212003831a6e4
3
+ metadata.gz: 6c2d36ee3f03672492a67bc2b98344a65051392dfaf0891aee1cbe1c01126884
4
+ data.tar.gz: 1e8ff7f7b02d804be0c4d077424cec4dfbb5881b3bcada145b1c2878e3913e2b
5
5
  SHA512:
6
- metadata.gz: 2cf99aec1c6d5738c4a71964a7ce67e70c8ade3150042c4e381fef19b9eaa82cf6638979bf97c6e9941f119304973f9e6f3fd31b3bab0fd635520344448b5587
7
- data.tar.gz: 2de5ab47951585b298e43bece202f3ad885ae8eb03edb2b6e860b30e3172ea213947509b9543bbf31df06ef03079ff1e7cfd1b7a32cd964428d9f9612fd7e171
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.13'
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
 
@@ -338,6 +312,33 @@ data:
338
312
  If you want to have i18n-tasks reorganize your existing keys using `data.write`, either set the router to
339
313
  `pattern_router` as above, or run `i18n-tasks normalize -p` (forcing the use of the pattern router for that run).
340
314
 
315
+ ##### Isolating router
316
+
317
+ Isolating router assumes each YAML file is independent and can contain similar keys.
318
+
319
+ As a result, the translations are written to an alternate target file for each source file
320
+ (only the `%{locale}` part is changed to match target locale). Thus, it is not necessary to
321
+ specify any `write` configuration (in fact, it would be completely ignored).
322
+
323
+ This can be useful for example when using [ViewComponent sidecars](https://viewcomponent.org/guide/translations.html)
324
+ (ViewComponent assigns an implicit scope to each sidecar YAML file but `i18n-tasks` is not aware of
325
+ that logic, resulting in collisions):
326
+
327
+ * `app/components/movies_component.en.yml`:
328
+ ```yaml
329
+ en:
330
+ title: Movies
331
+ ```
332
+
333
+ * `app/components/games_component.en.yml`
334
+ ```yaml
335
+ en:
336
+ title: Games
337
+ ```
338
+
339
+ This router has a limitation, though: it does not support detecting missing keys from code usage
340
+ (since it is not aware of the implicit scope logic).
341
+
341
342
  ##### Key pattern syntax
342
343
 
343
344
  A special syntax similar to file glob patterns is used throughout i18n-tasks to match translation keys:
@@ -408,20 +409,44 @@ Put the key in `GOOGLE_TRANSLATE_API_KEY` environment variable or in the config
408
409
  ```yaml
409
410
  # config/i18n-tasks.yml
410
411
  translation:
412
+ backend: google
411
413
  google_translate_api_key: <Google Translate API key>
412
414
  ```
413
415
 
416
+ or via environment variable:
417
+
418
+ ```bash
419
+ GOOGLE_TRANSLATE_API_KEY=<Google Translate API key>
420
+ ```
421
+
414
422
  <a name="deepl-translation-config"></a>
415
423
  ### DeepL Pro Translate
416
424
 
417
- `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.
418
426
 
419
427
  ```yaml
420
428
  # config/i18n-tasks.yml
421
429
  translation:
430
+ backend: deepl
422
431
  deepl_api_key: <DeepL Pro API key>
423
432
  deepl_host: <optional>
424
433
  deepl_version: <optional>
434
+ deepl_glossary_ids:
435
+ - f28106eb-0e06-489e-82c6-8215d6f95089
436
+ - 2c6415be-1852-4f54-9e1b-d800463496b4
437
+ deepl_options:
438
+ formality: prefer_less
439
+ deepl_locale_aliases:
440
+ en: en-us
441
+ pt: pt-br
442
+ ```
443
+
444
+ or via environment variables:
445
+
446
+ ```bash
447
+ DEEPL_API_KEY=<DeepL Pro API key>
448
+ DEEPL_HOST=<optional>
449
+ DEEPL_VERSION=<optional>
425
450
  ```
426
451
 
427
452
  <a name="yandex-translation-config"></a>
@@ -432,9 +457,16 @@ translation:
432
457
  ```yaml
433
458
  # config/i18n-tasks.yml
434
459
  translation:
460
+ backend: yandex
435
461
  yandex_api_key: <Yandex API key>
436
462
  ```
437
463
 
464
+ or via environment variable:
465
+
466
+ ```bash
467
+ YANDEX_API_KEY=<Yandex API key>
468
+ ```
469
+
438
470
  <a name="openai-translation-config"></a>
439
471
  ### OpenAI Translate
440
472
 
@@ -443,7 +475,62 @@ translation:
443
475
  ```yaml
444
476
  # config/i18n-tasks.yml
445
477
  translation:
478
+ backend: openai
446
479
  openai_api_key: <OpenAI API key>
480
+ openai_model: <optional>
481
+ ```
482
+
483
+ or via environment variable:
484
+
485
+ ```bash
486
+ OPENAI_API_KEY=<OpenAI API key>
487
+ OPENAI_MODEL=<optional>
488
+ ```
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"
447
534
  ```
448
535
 
449
536
  ## Interactive console
@@ -16,27 +16,26 @@ en:
16
16
  data_format: 'Data format: %{valid_text}.'
17
17
  keep_order: Keep the order of the keys
18
18
  key_pattern: Filter by key pattern (e.g. 'common.*')
19
- key_pattern_to_rename: Full key (pattern) to rename. Required
20
19
  locale: :i18n_tasks.common.locale
21
20
  locale_to_translate_from: Locale to translate from
22
21
  locales_filter: 'Locale(s) to process. Special: base'
23
22
  missing_types: 'Filter by types: %{valid}'
24
- new_key_name: New name, interpolates original name as %{key}. Required
25
23
  nostdin: Do not read from stdin
26
24
  out_format: 'Output format: %{valid_text}'
27
25
  pattern_router: 'Use pattern router: keys moved per config data.write'
28
26
  strict: >-
29
27
  Avoid inferring dynamic key usages such as t("cats.#{cat}.name"). Takes precedence over
30
28
  the config setting if set.
31
- translation_backend: Translation backend (google or deepl)
29
+ translation_backend: Translation backend [google, deepl, yandex, openai])
32
30
  value: >-
33
- Value. Interpolates: %{value}, %{human_key}, %{key}, %{default}, %{value_or_human_key},
34
- %{value_or_default_or_human_key}
31
+ Value. Interpolates: %%{value}, %%{human_key}, %%{key}, %%{default}, %%{value_or_human_key},
32
+ %%{value_or_default_or_human_key}
35
33
  desc:
36
34
  add_missing: add missing keys to locale data, optionally match a pattern
37
35
  check_consistent_interpolations: verify that all translations use correct interpolation variables
38
36
  check_normalized: verify that all translation data is normalized
39
37
  config: display i18n-tasks configuration
38
+ cp: copy the keys in locale data that match the given pattern
40
39
  data: show locale data
41
40
  data_merge: merge locale data with trees
42
41
  data_remove: remove keys present in tree from data
@@ -70,6 +69,7 @@ en:
70
69
  enum_opt:
71
70
  invalid: "%{invalid} is not one of: %{valid}."
72
71
  errors:
72
+ invalid_backend: 'Invalid backend: %{invalid}. Must be one of %{valid}.'
73
73
  invalid_format: 'invalid format: %{invalid}. valid: %{valid}.'
74
74
  invalid_locale: 'invalid locale: %{invalid}'
75
75
  invalid_missing_type:
@@ -130,6 +130,15 @@ en:
130
130
  none: Every translation is in use.
131
131
  usages:
132
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.
133
142
  yandex_translate:
134
143
  errors:
135
144
  no_api_key: >-
@@ -13,28 +13,27 @@ ru:
13
13
  data_format: 'Формат данных: %{valid_text}.'
14
14
  keep_order: Keep the order of the keys
15
15
  key_pattern: Маска ключа (например, common.*)
16
- key_pattern_to_rename: Полный ключ (шаблон) для переименования. Необходимый параметр.
17
16
  locale: 'Язык. По умолчанию: base'
18
17
  locale_to_translate_from: 'Язык, с которого переводить (по умолчанию: base)'
19
18
  locales_filter: >-
20
19
  Список языков для обработки, разделенный запятыми (,). По умолчанию: все. Специальное
21
20
  значение: base.
22
21
  missing_types: 'Типы недостающих переводов: %{valid}. По умолчанию: все'
23
- new_key_name: Новое имя, интерполирует оригинальное название как %{key}. Необходимый параметр.
24
22
  nostdin: Не читать дерево из стандартного ввода
25
23
  out_format: 'Формат вывода: %{valid_text}.'
26
24
  pattern_router: 'Использовать pattern_router: ключи распределятся по файлам согласно data.write'
27
25
  strict: Не угадывать динамические использования ключей, например `t("category.#{category.key}")`
28
- translation_backend: Движок перевода (google или deepl)
26
+ translation_backend: Движок перевода [google, deepl, yandex, openai]
29
27
  value: >-
30
- Значение, интерполируется с %{value}, %{human_key}, %{key}, %{default}, %{value_or_human_key},
31
- %{value_or_default_or_human_key}
28
+ Значение, интерполируется с %%{value}, %%{human_key}, %%{key}, %%{default}, %%{value_or_human_key},
29
+ %%{value_or_default_or_human_key}
32
30
  desc:
33
31
  add_missing: добавить недостающие ключи к переводам
34
32
  check_consistent_interpolations: убедитесь, что во всех переводах используются правильные
35
33
  интерполяционные переменные
36
34
  check_normalized: проверить, что все файлы переводов нормализованы
37
35
  config: показать конфигурацию
36
+ cp: скопируйте ключи в данных локали, соответствующие заданному шаблону
38
37
  data: показать данные переводов
39
38
  data_merge: добавить дерево к переводам
40
39
  data_remove: удалить ключи, которые есть в дереве, из данных
@@ -67,6 +66,7 @@ ru:
67
66
  enum_opt:
68
67
  invalid: "%{invalid} не является одним из: %{valid}."
69
68
  errors:
69
+ invalid_backend: 'Недопустимый источник данных: %{invalid}. Должен быть одним из %{valid}.'
70
70
  invalid_format: 'Неизвестный формат %{invalid}. Форматы: %{valid}.'
71
71
  invalid_locale: Неверный язык %{invalid}
72
72
  invalid_missing_type:
@@ -131,6 +131,15 @@ ru:
131
131
  none: Все переводы используются.
132
132
  usages:
133
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 не вернул результатов.
134
143
  yandex_translate:
135
144
  errors:
136
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
 
@@ -39,13 +41,13 @@ Gem::Specification.new do |s|
39
41
 
40
42
  s.add_dependency 'activesupport', '>= 4.0.2'
41
43
  s.add_dependency 'ast', '>= 2.1.0'
42
- s.add_dependency 'better_html', '>= 1.0', '< 3.0'
43
44
  s.add_dependency 'erubi'
44
45
  s.add_dependency 'highline', '>= 2.0.0'
45
46
  s.add_dependency 'i18n'
46
47
  s.add_dependency 'parser', '>= 3.2.2.1'
47
48
  s.add_dependency 'rails-i18n'
48
49
  s.add_dependency 'rainbow', '>= 2.2.2', '< 4.0'
50
+ s.add_dependency 'ruby-progressbar', '~> 1.8', '>= 1.8.1'
49
51
  s.add_dependency 'terminal-table', '>= 1.5.1'
50
52
  s.add_development_dependency 'bundler', '~> 2.0', '>= 2.0.1'
51
53
  s.add_development_dependency 'overcommit', '~> 0.58.0'
@@ -46,6 +46,20 @@ module I18n::Tasks
46
46
  terminal_report.mv_results results
47
47
  end
48
48
 
49
+ cmd :cp,
50
+ pos: 'FROM_KEY_PATTERN TO_KEY_PATTERN',
51
+ desc: t('i18n_tasks.cmd.desc.cp')
52
+ def cp(opt = {})
53
+ fail CommandError, 'requires FROM_KEY_PATTERN and TO_KEY_PATTERN' if opt[:arguments].size < 2
54
+
55
+ from_pattern = opt[:arguments].shift
56
+ to_pattern = opt[:arguments].shift
57
+ forest = i18n.data_forest
58
+ results = forest.mv_key!(compile_key_pattern(from_pattern), to_pattern, root: false, retain: true)
59
+ i18n.data.write forest
60
+ terminal_report.cp_results results
61
+ end
62
+
49
63
  cmd :rm,
50
64
  pos: 'KEY_PATTERN [KEY_PATTERN...]',
51
65
  desc: t('i18n_tasks.cmd.desc.rm')
@@ -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
@@ -26,7 +26,7 @@ module I18n::Tasks
26
26
  arg :value,
27
27
  '-v',
28
28
  '--value VALUE',
29
- t('i18n_tasks.cmd.args.desc.value')
29
+ t('i18n_tasks.cmd.args.desc.value', dummy: 'value') # Dummy value is workaround for https://github.com/ruby-i18n/i18n/issues/689
30
30
 
31
31
  arg :config,
32
32
  '-c',
@@ -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,14 +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')
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')
70
76
  conf[:yandex_api_key] = ENV['YANDEX_API_KEY'] if ENV.key?('YANDEX_API_KEY')
71
77
  conf
72
78
  end
@@ -3,6 +3,7 @@
3
3
  require 'i18n/tasks/data/tree/node'
4
4
  require 'i18n/tasks/data/router/pattern_router'
5
5
  require 'i18n/tasks/data/router/conservative_router'
6
+ require 'i18n/tasks/data/router/isolating_router'
6
7
  require 'i18n/tasks/data/file_formats'
7
8
  require 'i18n/tasks/key_pattern_matching'
8
9
 
@@ -151,6 +152,7 @@ module I18n::Tasks
151
152
 
152
153
  ROUTER_NAME_ALIASES = {
153
154
  'conservative_router' => 'I18n::Tasks::Data::Router::ConservativeRouter',
155
+ 'isolating_router' => 'I18n::Tasks::Data::Router::IsolatingRouter',
154
156
  'pattern_router' => 'I18n::Tasks::Data::Router::PatternRouter'
155
157
  }.freeze
156
158
  def router
@@ -170,6 +172,9 @@ module I18n::Tasks
170
172
  end.map do |path|
171
173
  [path.freeze, load_file(path) || {}]
172
174
  end.map do |path, data|
175
+ if router.is_a?(I18n::Tasks::Data::Router::IsolatingRouter)
176
+ data.transform_values! { |tree| { "<#{router.alternate_path_for(path, base_locale)}>" => tree } }
177
+ end
173
178
  filter_nil_keys! path, data
174
179
  Data::Tree::Siblings.from_nested_hash(data).tap do |s|
175
180
  s.leaves { |x| x.data.update(path: path, locale: locale) }