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.
- checksums.yaml +4 -4
- data/README.md +125 -38
- data/config/locales/en.yml +14 -5
- data/config/locales/ru.yml +14 -5
- data/i18n-tasks.gemspec +4 -2
- data/lib/i18n/tasks/command/commands/data.rb +14 -0
- 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/common.rb +1 -1
- data/lib/i18n/tasks/command/options/locales.rb +12 -3
- data/lib/i18n/tasks/configuration.rb +8 -2
- data/lib/i18n/tasks/data/file_system_base.rb +5 -0
- data/lib/i18n/tasks/data/router/isolating_router.rb +146 -0
- data/lib/i18n/tasks/data/tree/siblings.rb +2 -2
- data/lib/i18n/tasks/interpolations.rb +1 -1
- data/lib/i18n/tasks/key_pattern_matching.rb +4 -4
- data/lib/i18n/tasks/reports/terminal.rb +6 -0
- data/lib/i18n/tasks/scanners/ast_matchers/default_i18n_subject_matcher.rb +26 -0
- data/lib/i18n/tasks/scanners/ast_matchers/rails_model_matcher.rb +1 -1
- data/lib/i18n/tasks/scanners/erb_ast_scanner.rb +55 -25
- 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/relative_keys.rb +1 -1
- data/lib/i18n/tasks/scanners/ruby_ast_scanner.rb +5 -4
- 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/split_key.rb +30 -47
- data/lib/i18n/tasks/translation.rb +4 -1
- data/lib/i18n/tasks/translators/base_translator.rb +11 -1
- data/lib/i18n/tasks/translators/deepl_translator.rb +32 -1
- data/lib/i18n/tasks/translators/google_translator.rb +35 -12
- data/lib/i18n/tasks/translators/openai_translator.rb +55 -23
- 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 +1 -0
- data/lib/i18n/tasks/version.rb +1 -1
- data/lib/i18n/tasks.rb +1 -0
- data/templates/config/i18n-tasks.yml +26 -3
- metadata +33 -26
- 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:
|
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
|
|
@@ -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
|
data/config/locales/en.yml
CHANGED
@@ -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
|
29
|
+
translation_backend: Translation backend [google, deepl, yandex, openai])
|
32
30
|
value: >-
|
33
|
-
Value. Interpolates:
|
34
|
-
|
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: >-
|
data/config/locales/ru.yml
CHANGED
@@ -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: Движок перевода
|
26
|
+
translation_backend: Движок перевода [google, deepl, yandex, openai]
|
29
27
|
value: >-
|
30
|
-
Значение, интерполируется с
|
31
|
-
|
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
|
-
|
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
|
@@ -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:
|
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,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) }
|