i18n-tasks 0.9.37 → 1.0.13
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 +43 -5
- data/Rakefile +1 -1
- data/config/locales/en.yml +12 -3
- data/config/locales/ru.yml +8 -0
- data/i18n-tasks.gemspec +14 -7
- data/lib/i18n/tasks/cli.rb +8 -7
- data/lib/i18n/tasks/command/commander.rb +1 -0
- data/lib/i18n/tasks/command/commands/missing.rb +17 -5
- data/lib/i18n/tasks/command/options/common.rb +0 -1
- data/lib/i18n/tasks/command/options/data.rb +1 -1
- data/lib/i18n/tasks/concurrent/cached_value.rb +0 -2
- data/lib/i18n/tasks/configuration.rb +13 -7
- data/lib/i18n/tasks/data/adapter/yaml_adapter.rb +15 -2
- data/lib/i18n/tasks/data/file_formats.rb +1 -1
- data/lib/i18n/tasks/data/router/pattern_router.rb +1 -1
- data/lib/i18n/tasks/data/tree/node.rb +1 -1
- data/lib/i18n/tasks/data/tree/nodes.rb +5 -7
- data/lib/i18n/tasks/data/tree/siblings.rb +1 -2
- data/lib/i18n/tasks/data/tree/traversal.rb +25 -11
- data/lib/i18n/tasks/html_keys.rb +2 -2
- data/lib/i18n/tasks/interpolations.rb +7 -3
- data/lib/i18n/tasks/key_pattern_matching.rb +2 -0
- data/lib/i18n/tasks/locale_pathname.rb +1 -1
- data/lib/i18n/tasks/plural_keys.rb +0 -6
- data/lib/i18n/tasks/references.rb +3 -3
- data/lib/i18n/tasks/reports/base.rb +2 -2
- data/lib/i18n/tasks/reports/terminal.rb +3 -3
- data/lib/i18n/tasks/scanners/ast_matchers/base_matcher.rb +118 -0
- data/lib/i18n/tasks/scanners/ast_matchers/message_receivers_matcher.rb +91 -0
- data/lib/i18n/tasks/scanners/ast_matchers/rails_model_matcher.rb +69 -0
- data/lib/i18n/tasks/scanners/erb_ast_processor.rb +74 -0
- data/lib/i18n/tasks/scanners/erb_ast_scanner.rb +48 -0
- data/lib/i18n/tasks/scanners/local_ruby_parser.rb +85 -0
- data/lib/i18n/tasks/scanners/pattern_mapper.rb +1 -1
- data/lib/i18n/tasks/scanners/pattern_scanner.rb +2 -2
- data/lib/i18n/tasks/scanners/relative_keys.rb +2 -2
- data/lib/i18n/tasks/scanners/results/occurrence.rb +17 -1
- data/lib/i18n/tasks/scanners/ruby_ast_call_finder.rb +9 -34
- data/lib/i18n/tasks/scanners/ruby_ast_scanner.rb +91 -156
- data/lib/i18n/tasks/scanners/ruby_key_literals.rb +2 -2
- data/lib/i18n/tasks/split_key.rb +1 -1
- data/lib/i18n/tasks/translation.rb +4 -1
- data/lib/i18n/tasks/translators/base_translator.rb +17 -12
- data/lib/i18n/tasks/translators/deepl_translator.rb +34 -11
- data/lib/i18n/tasks/translators/google_translator.rb +1 -1
- data/lib/i18n/tasks/translators/openai_translator.rb +100 -0
- data/lib/i18n/tasks/used_keys.rb +9 -6
- data/lib/i18n/tasks/version.rb +1 -1
- data/lib/i18n/tasks.rb +11 -0
- data/templates/config/i18n-tasks.yml +17 -4
- data/templates/minitest/i18n_test.rb +6 -6
- metadata +74 -16
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 12e19e4d7fe61bd20e000b0b6e4db5bdf233443e8e8cf965b62e634bdfa3e5a2
|
4
|
+
data.tar.gz: 3dab05338d079c4defad590466ab2985faf43245d616f86af4c212003831a6e4
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 2cf99aec1c6d5738c4a71964a7ce67e70c8ade3150042c4e381fef19b9eaa82cf6638979bf97c6e9941f119304973f9e6f3fd31b3bab0fd635520344448b5587
|
7
|
+
data.tar.gz: 2de5ab47951585b298e43bece202f3ad885ae8eb03edb2b6e860b30e3172ea213947509b9543bbf31df06ef03079ff1e7cfd1b7a32cd964428d9f9612fd7e171
|
data/README.md
CHANGED
@@ -1,5 +1,7 @@
|
|
1
1
|
# i18n-tasks [![Build Status][badge-ci]][ci] [![Coverage Status][badge-coverage]][coverage] [](https://gitter.im/glebm/i18n-tasks?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
2
2
|
|
3
|
+
[](https://stand-with-ukraine.pp.ua/)
|
4
|
+
|
3
5
|
i18n-tasks helps you find and manage missing and unused translations.
|
4
6
|
|
5
7
|
<img width="539" height="331" src="https://i.imgur.com/XZBd8l7.png">
|
@@ -22,7 +24,7 @@ i18n-tasks can be used with any project using the ruby [i18n gem][i18n-gem] (def
|
|
22
24
|
Add i18n-tasks to the Gemfile:
|
23
25
|
|
24
26
|
```ruby
|
25
|
-
gem 'i18n-tasks', '~> 0.
|
27
|
+
gem 'i18n-tasks', '~> 1.0.13'
|
26
28
|
```
|
27
29
|
|
28
30
|
Copy the default [configuration file](#configuration):
|
@@ -115,6 +117,17 @@ $ i18n-tasks translate-missing --backend=yandex
|
|
115
117
|
$ i18n-tasks translate-missing --from=en es fr
|
116
118
|
```
|
117
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
|
+
```
|
130
|
+
|
118
131
|
### Find usages
|
119
132
|
|
120
133
|
See where the keys are used with `i18n-tasks find`:
|
@@ -221,7 +234,7 @@ See the full list of tasks with `i18n-tasks --help`.
|
|
221
234
|
|
222
235
|
### Features and limitations
|
223
236
|
|
224
|
-
`i18n-tasks` uses an AST scanner for `.rb` files, and a regexp-based scanner for other files, such as `.haml`.
|
237
|
+
`i18n-tasks` uses an AST scanner for `.rb` and `.html.erb` files, and a regexp-based scanner for other files, such as `.haml`.
|
225
238
|
|
226
239
|
#### Relative keys
|
227
240
|
|
@@ -333,6 +346,7 @@ A special syntax similar to file glob patterns is used throughout i18n-tasks to
|
|
333
346
|
|:------------:|:----------------------------------------------------------|
|
334
347
|
| `*` | matches everything |
|
335
348
|
| `:` | matches a single key |
|
349
|
+
| `*:` | matches part of a single key |
|
336
350
|
| `{a, b.c}` | match any in set, can use `:` and `*`, match is captured |
|
337
351
|
|
338
352
|
Example of usage:
|
@@ -353,7 +367,7 @@ If you have implemented a custom adapter please share it on [the wiki][wiki].
|
|
353
367
|
|
354
368
|
### Usage search
|
355
369
|
|
356
|
-
i18n-tasks uses an AST scanner for `.rb` files, and a regexp scanner for all other files.
|
370
|
+
i18n-tasks uses an AST scanner for `.rb` and `.html.erb` files, and a regexp scanner for all other files.
|
357
371
|
New scanners can be added easily: please refer to [this example](https://github.com/glebm/i18n-tasks/wiki/A-custom-scanner-example).
|
358
372
|
|
359
373
|
See the `search` section in the [config file][config] for all available configuration options.
|
@@ -421,6 +435,17 @@ translation:
|
|
421
435
|
yandex_api_key: <Yandex API key>
|
422
436
|
```
|
423
437
|
|
438
|
+
<a name="openai-translation-config"></a>
|
439
|
+
### OpenAI Translate
|
440
|
+
|
441
|
+
`i18n-tasks translate-missing` requires a OpenAI API key, get it at [OpenAI](https://openai.com/).
|
442
|
+
|
443
|
+
```yaml
|
444
|
+
# config/i18n-tasks.yml
|
445
|
+
translation:
|
446
|
+
openai_api_key: <OpenAI API key>
|
447
|
+
```
|
448
|
+
|
424
449
|
## Interactive console
|
425
450
|
|
426
451
|
`i18n-tasks irb` starts an IRB session in i18n-tasks context. Type `guide` for more information.
|
@@ -434,15 +459,28 @@ See [i18n-tasks wiki: CSV import and export tasks](https://github.com/glebm/i18n
|
|
434
459
|
Tasks that come with the gem are defined in [lib/i18n/tasks/command/commands](lib/i18n/tasks/command/commands).
|
435
460
|
Custom tasks can be added easily, see the examples [on the wiki](https://github.com/glebm/i18n-tasks/wiki#custom-tasks).
|
436
461
|
|
462
|
+
# Development
|
463
|
+
|
464
|
+
- Install dependencies using `bundle install`
|
465
|
+
- Run tests using `bundle exec rspec`
|
466
|
+
- Install [Overcommit](https://github.com/sds/overcommit) by running `overcommit --install`
|
467
|
+
|
468
|
+
## Skip Overcommit-hooks
|
469
|
+
|
470
|
+
- `SKIP=RuboCop git commit`
|
471
|
+
- `OVERCOMMIT_DISABLE=1 git commit`
|
472
|
+
|
473
|
+
|
437
474
|
[MIT license]: /LICENSE.txt
|
438
475
|
[ci]: https://github.com/glebm/i18n-tasks/actions/workflows/tests.yml
|
439
476
|
[badge-ci]: https://github.com/glebm/i18n-tasks/actions/workflows/tests.yml/badge.svg
|
440
477
|
[coverage]: https://codeclimate.com/github/glebm/i18n-tasks
|
441
478
|
[badge-coverage]: https://api.codeclimate.com/v1/badges/5d173e90ada8df07cedc/test_coverage
|
442
|
-
[config]: https://github.com/glebm/i18n-tasks/blob/
|
479
|
+
[config]: https://github.com/glebm/i18n-tasks/blob/main/templates/config/i18n-tasks.yml
|
443
480
|
[wiki]: https://github.com/glebm/i18n-tasks/wiki "i18n-tasks wiki"
|
444
481
|
[i18n-gem]: https://github.com/svenfuchs/i18n "svenfuchs/i18n on Github"
|
445
482
|
[screenshot-i18n-tasks]: https://i.imgur.com/XZBd8l7.png "i18n-tasks screenshot"
|
446
483
|
[screenshot-find]: https://i.imgur.com/VxBrSfY.png "i18n-tasks find output screenshot"
|
447
|
-
[adapter-example]: https://github.com/glebm/i18n-tasks/blob/
|
484
|
+
[adapter-example]: https://github.com/glebm/i18n-tasks/blob/main/lib/i18n/tasks/data/file_system_base.rb
|
448
485
|
[custom-scanner-docs]: https://github.com/glebm/i18n-tasks/wiki/A-custom-scanner-example
|
486
|
+
[overcommit]: https://github.com/sds/overcommit#installation
|
data/Rakefile
CHANGED
data/config/locales/en.yml
CHANGED
@@ -33,7 +33,7 @@ en:
|
|
33
33
|
Value. Interpolates: %{value}, %{human_key}, %{key}, %{default}, %{value_or_human_key},
|
34
34
|
%{value_or_default_or_human_key}
|
35
35
|
desc:
|
36
|
-
add_missing: add missing keys to locale data
|
36
|
+
add_missing: add missing keys to locale data, optionally match a pattern
|
37
37
|
check_consistent_interpolations: verify that all translations use correct interpolation variables
|
38
38
|
check_normalized: verify that all translation data is normalized
|
39
39
|
config: display i18n-tasks configuration
|
@@ -46,12 +46,13 @@ en:
|
|
46
46
|
gem_path: show path to the gem
|
47
47
|
health: is everything OK?
|
48
48
|
irb: start REPL session within i18n-tasks context
|
49
|
-
missing: show missing translations
|
49
|
+
missing: show missing translations, optionally match a pattern
|
50
50
|
mv: rename/merge the keys in locale data that match the given pattern
|
51
51
|
normalize: 'normalize translation data: sort and move to the right files'
|
52
52
|
remove_unused: remove unused keys
|
53
53
|
rm: remove the keys in locale data that match the given pattern
|
54
|
-
translate_missing: translate missing keys with Google Translate or DeepL Pro
|
54
|
+
translate_missing: translate missing keys with Google Translate or DeepL Pro, optionally match
|
55
|
+
a pattern
|
55
56
|
tree_convert: convert tree between formats
|
56
57
|
tree_filter: filter tree by key pattern
|
57
58
|
tree_merge: merge trees
|
@@ -95,6 +96,8 @@ en:
|
|
95
96
|
Setup DeepL Pro API key via DEEPL_AUTH_KEY environment variable or translation.deepl_api_key
|
96
97
|
in config/i18n-tasks.yml. Get the key at https://www.deepl.com/pro.
|
97
98
|
no_results: DeepL returned no results.
|
99
|
+
specific_target_missing: You must supply a specific variant for the given target language
|
100
|
+
e.g. en-us instead of en.
|
98
101
|
google_translate:
|
99
102
|
errors:
|
100
103
|
no_api_key: >-
|
@@ -109,6 +112,12 @@ en:
|
|
109
112
|
missing:
|
110
113
|
details_title: Value in other locales or source
|
111
114
|
none: No translations are missing.
|
115
|
+
openai_translate:
|
116
|
+
errors:
|
117
|
+
no_api_key: >-
|
118
|
+
Set OpenAI API key via OPENAI_API_KEY environment variable or translation.openai_api_key
|
119
|
+
in config/i18n-tasks.yml. Get the key at https://openai.com/.
|
120
|
+
no_results: OpenAI returned no results.
|
112
121
|
remove_unused:
|
113
122
|
confirm:
|
114
123
|
one: "%{count} translation will be removed from %{locales}."
|
data/config/locales/ru.yml
CHANGED
@@ -94,6 +94,8 @@ ru:
|
|
94
94
|
Задайте ключ API DeepL через переменную окружения DEEPL_AUTH_KEY или translation.deepl_api_key
|
95
95
|
Получите ключ через https://www.deepl.com/pro.
|
96
96
|
no_results: DeepL не дал результатов.
|
97
|
+
specific_target_missing: You must supply a specific variant for the given target language
|
98
|
+
e.g. en-us instead of en.
|
97
99
|
google_translate:
|
98
100
|
errors:
|
99
101
|
no_api_key: >-
|
@@ -109,6 +111,12 @@ ru:
|
|
109
111
|
missing:
|
110
112
|
details_title: На других языках или в коде
|
111
113
|
none: Всё переведено.
|
114
|
+
openai_translate:
|
115
|
+
errors:
|
116
|
+
no_api_key: |-
|
117
|
+
Установить ключ API Яндекса с помощью переменной среды OPENAI_API_KEY или translation.openai_api_key
|
118
|
+
в config / i18n-tasks.yml. Получите ключ по адресу https://openai.com/.
|
119
|
+
no_results: Яндекс не дал результатов.
|
112
120
|
remove_unused:
|
113
121
|
confirm:
|
114
122
|
few: Переводы (%{count}) будут удалены из %{locales}.
|
data/i18n-tasks.gemspec
CHANGED
@@ -4,7 +4,7 @@ lib = File.expand_path('lib', __dir__)
|
|
4
4
|
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
5
5
|
require 'i18n/tasks/version'
|
6
6
|
|
7
|
-
Gem::Specification.new do |s|
|
7
|
+
Gem::Specification.new do |s|
|
8
8
|
s.name = 'i18n-tasks'
|
9
9
|
s.version = I18n::Tasks::VERSION
|
10
10
|
s.authors = ['glebm']
|
@@ -22,31 +22,38 @@ Gem::Specification.new do |s| # rubocop:disable Metrics/BlockLength
|
|
22
22
|
cp $(bundle exec i18n-tasks gem-path)/templates/config/i18n-tasks.yml config/
|
23
23
|
# Add an RSpec for missing and unused keys:
|
24
24
|
cp $(bundle exec i18n-tasks gem-path)/templates/rspec/i18n_spec.rb spec/
|
25
|
+
# Or for minitest:
|
26
|
+
cp $(bundle exec i18n-tasks gem-path)/templates/minitest/i18n_test.rb test/
|
25
27
|
TEXT
|
26
28
|
s.homepage = 'https://github.com/glebm/i18n-tasks'
|
27
|
-
s.metadata = {
|
28
|
-
|
29
|
+
s.metadata = {
|
30
|
+
'issue_tracker' => 'https://github.com/glebm/i18n-tasks',
|
31
|
+
'rubygems_mfa_required' => 'true'
|
32
|
+
}
|
33
|
+
s.required_ruby_version = '>= 2.6', '< 4.0'
|
29
34
|
|
30
35
|
s.files = `git ls-files`.split($/)
|
31
36
|
s.files -= s.files.grep(%r{^(doc/|\.|spec/)}) + %w[CHANGES.md config/i18n-tasks.yml Gemfile]
|
32
37
|
s.executables = s.files.grep(%r{^bin/}) { |f| File.basename(f) } - %w[i18n-tasks.cmd]
|
33
|
-
s.test_files = s.files.grep(%r{^(test|spec|features)/})
|
34
38
|
s.require_paths = ['lib']
|
35
39
|
|
36
40
|
s.add_dependency 'activesupport', '>= 4.0.2'
|
37
41
|
s.add_dependency 'ast', '>= 2.1.0'
|
42
|
+
s.add_dependency 'better_html', '>= 1.0', '< 3.0'
|
38
43
|
s.add_dependency 'erubi'
|
39
44
|
s.add_dependency 'highline', '>= 2.0.0'
|
40
45
|
s.add_dependency 'i18n'
|
41
|
-
s.add_dependency 'parser', '>= 2.2.
|
46
|
+
s.add_dependency 'parser', '>= 3.2.2.1'
|
42
47
|
s.add_dependency 'rails-i18n'
|
43
48
|
s.add_dependency 'rainbow', '>= 2.2.2', '< 4.0'
|
44
49
|
s.add_dependency 'terminal-table', '>= 1.5.1'
|
45
|
-
s.add_development_dependency 'axlsx', '~> 2.0'
|
46
50
|
s.add_development_dependency 'bundler', '~> 2.0', '>= 2.0.1'
|
51
|
+
s.add_development_dependency 'overcommit', '~> 0.58.0'
|
47
52
|
s.add_development_dependency 'rake'
|
48
53
|
s.add_development_dependency 'rspec', '~> 3.3'
|
49
|
-
s.add_development_dependency 'rubocop', '~> 1.
|
54
|
+
s.add_development_dependency 'rubocop', '~> 1.50.1'
|
55
|
+
s.add_development_dependency 'rubocop-rake', '~> 0.6.0'
|
56
|
+
s.add_development_dependency 'rubocop-rspec', '~> 2.19.0'
|
50
57
|
s.add_development_dependency 'simplecov'
|
51
58
|
s.add_development_dependency 'yard'
|
52
59
|
|
data/lib/i18n/tasks/cli.rb
CHANGED
@@ -35,13 +35,14 @@ class I18n::Tasks::CLI
|
|
35
35
|
|
36
36
|
def run(argv)
|
37
37
|
argv.each_with_index do |arg, i|
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
38
|
+
next unless ['--config', '-c'].include?(arg)
|
39
|
+
|
40
|
+
_, config_file = argv.slice!(i, 2)
|
41
|
+
if File.exist?(config_file)
|
42
|
+
@config_file = config_file
|
43
|
+
break
|
44
|
+
else
|
45
|
+
error "Config file doesn't exist: #{config_file}", 128
|
45
46
|
end
|
46
47
|
end
|
47
48
|
|
@@ -16,6 +16,7 @@ module I18n::Tasks
|
|
16
16
|
end
|
17
17
|
|
18
18
|
def run(name, opts = {})
|
19
|
+
log_stderr "#{Rainbow('#StandWith').bg(:blue)}#{Rainbow('Ukraine').bg(:yellow)}"
|
19
20
|
name = name.to_sym
|
20
21
|
public_name = name.to_s.tr '_', '-'
|
21
22
|
log_verbose "task: #{public_name}(#{opts.map { |k, v| "#{k}: #{v.inspect}" } * ', '})"
|
@@ -25,10 +25,14 @@ module I18n::Tasks
|
|
25
25
|
cmd :missing,
|
26
26
|
pos: '[locale ...]',
|
27
27
|
desc: t('i18n_tasks.cmd.desc.missing'),
|
28
|
-
args: %i[locales out_format missing_types]
|
28
|
+
args: %i[locales out_format missing_types pattern]
|
29
29
|
|
30
30
|
def missing(opt = {})
|
31
31
|
forest = i18n.missing_keys(**opt.slice(:locales, :base_locale, :types))
|
32
|
+
if opt[:pattern]
|
33
|
+
pattern_re = i18n.compile_key_pattern(opt[:pattern])
|
34
|
+
forest.select_keys! { |full_key, _node| full_key =~ pattern_re }
|
35
|
+
end
|
32
36
|
print_forest forest, opt, :missing_keys
|
33
37
|
:exit1 unless forest.empty?
|
34
38
|
end
|
@@ -36,10 +40,14 @@ module I18n::Tasks
|
|
36
40
|
cmd :translate_missing,
|
37
41
|
pos: '[locale ...]',
|
38
42
|
desc: t('i18n_tasks.cmd.desc.translate_missing'),
|
39
|
-
args: [:locales, :locale_to_translate_from, arg(:out_format).from(1), :translation_backend]
|
43
|
+
args: [:locales, :locale_to_translate_from, arg(:out_format).from(1), :translation_backend, :pattern]
|
40
44
|
|
41
45
|
def translate_missing(opt = {})
|
42
|
-
missing
|
46
|
+
missing = i18n.missing_diff_forest opt[:locales], opt[:from]
|
47
|
+
if opt[:pattern]
|
48
|
+
pattern_re = i18n.compile_key_pattern(opt[:pattern])
|
49
|
+
missing.select_keys! { |full_key, _node| full_key =~ pattern_re }
|
50
|
+
end
|
43
51
|
translated = i18n.translate_forest missing, from: opt[:from], backend: opt[:backend].to_sym
|
44
52
|
i18n.data.merge! translated
|
45
53
|
log_stderr t('i18n_tasks.translate_missing.translated', count: translated.leaves.count)
|
@@ -49,17 +57,21 @@ module I18n::Tasks
|
|
49
57
|
cmd :add_missing,
|
50
58
|
pos: '[locale ...]',
|
51
59
|
desc: t('i18n_tasks.cmd.desc.add_missing'),
|
52
|
-
args: [:locales, :out_format, arg(:value) + [{ default: '%{value_or_default_or_human_key}' }],
|
60
|
+
args: [:locales, :out_format, :pattern, arg(:value) + [{ default: '%{value_or_default_or_human_key}' }],
|
53
61
|
['--nil-value', 'Set value to nil. Takes precedence over the value argument.']]
|
54
62
|
|
55
63
|
# Merge base locale first, as this may affect the value for the other locales
|
56
|
-
def add_missing(opt = {})
|
64
|
+
def add_missing(opt = {}) # rubocop:disable Metrics/AbcSize
|
57
65
|
[
|
58
66
|
[i18n.base_locale] & opt[:locales],
|
59
67
|
opt[:locales] - [i18n.base_locale]
|
60
68
|
].reject(&:empty?).each_with_object(i18n.empty_forest) do |locales, added|
|
61
69
|
forest = i18n.missing_keys(locales: locales, **opt.slice(:types, :base_locale))
|
62
70
|
.set_each_value!(opt[:'nil-value'] ? nil : opt[:value])
|
71
|
+
if opt[:pattern]
|
72
|
+
pattern_re = i18n.compile_key_pattern(opt[:pattern])
|
73
|
+
forest.select_keys! { |full_key, _node| full_key =~ pattern_re }
|
74
|
+
end
|
63
75
|
i18n.data.merge! forest
|
64
76
|
added.merge! forest
|
65
77
|
end.tap do |added|
|
@@ -86,7 +86,7 @@ module I18n::Tasks
|
|
86
86
|
when 'keys'
|
87
87
|
puts forest.key_names(root: true)
|
88
88
|
when 'key-values'
|
89
|
-
puts
|
89
|
+
puts(forest.key_values(root: true).map { |kv| kv.join("\t") })
|
90
90
|
when *DATA_FORMATS
|
91
91
|
puts i18n.data.adapter_dump forest.to_hash(true), format
|
92
92
|
end
|
@@ -30,6 +30,13 @@ module I18n::Tasks::Configuration # rubocop:disable Metrics/ModuleLength
|
|
30
30
|
warn_deprecated 'Please move relative_roots under search in config/i18n-tasks.yml.'
|
31
31
|
c[:search][:relative_roots] = c.delete(:relative_roots)
|
32
32
|
end
|
33
|
+
|
34
|
+
if c.dig(:search, :exclude_method_name_paths)
|
35
|
+
warn_deprecated(
|
36
|
+
'Please rename exclude_method_name_paths to relative_exclude_method_name_paths in config/i18n-tasks.yml.'
|
37
|
+
)
|
38
|
+
c[:search][:relative_exclude_method_name_paths] = c[:search].delete(:exclude_method_name_paths)
|
39
|
+
end
|
33
40
|
end
|
34
41
|
else
|
35
42
|
{}.with_indifferent_access
|
@@ -44,12 +51,10 @@ module I18n::Tasks::Configuration # rubocop:disable Metrics/ModuleLength
|
|
44
51
|
# data config
|
45
52
|
# @return [Hash<adapter: String, options: Hash>]
|
46
53
|
def data_config
|
47
|
-
@config_sections[:data] ||=
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
}
|
52
|
-
end
|
54
|
+
@config_sections[:data] ||= {
|
55
|
+
adapter: data.class.name,
|
56
|
+
config: data.config
|
57
|
+
}
|
53
58
|
end
|
54
59
|
|
55
60
|
# translation config
|
@@ -61,6 +66,7 @@ module I18n::Tasks::Configuration # rubocop:disable Metrics/ModuleLength
|
|
61
66
|
conf[:deepl_api_key] = ENV['DEEPL_AUTH_KEY'] if ENV.key?('DEEPL_AUTH_KEY')
|
62
67
|
conf[:deepl_host] = ENV['DEEPL_HOST'] if ENV.key?('DEEPL_HOST')
|
63
68
|
conf[:deepl_version] = ENV['DEEPL_VERSION'] if ENV.key?('DEEPL_VERSION')
|
69
|
+
conf[:openai_api_key] = ENV['OPENAI_API_KEY'] if ENV.key?('OPENAI_API_KEY')
|
64
70
|
conf[:yandex_api_key] = ENV['YANDEX_API_KEY'] if ENV.key?('YANDEX_API_KEY')
|
65
71
|
conf
|
66
72
|
end
|
@@ -82,7 +88,7 @@ module I18n::Tasks::Configuration # rubocop:disable Metrics/ModuleLength
|
|
82
88
|
valid_locales = Dir[File.join(I18n::Tasks.gem_path, 'config', 'locales', '*.yml')]
|
83
89
|
.map { |f| File.basename(f, '.yml') }
|
84
90
|
unless valid_locales.include?(internal_locale)
|
85
|
-
log_warn "invalid internal_locale #{internal_locale.inspect}. "\
|
91
|
+
log_warn "invalid internal_locale #{internal_locale.inspect}. " \
|
86
92
|
"Available internal locales: #{valid_locales * ', '}."
|
87
93
|
internal_locale = DEFAULTS[:internal_locale].to_s
|
88
94
|
end
|
@@ -5,11 +5,14 @@ module I18n::Tasks
|
|
5
5
|
module Data
|
6
6
|
module Adapter
|
7
7
|
module YamlAdapter
|
8
|
+
EMOJI_REGEX = /\\u[\da-f]{8}/i.freeze
|
9
|
+
TRAILING_SPACE_REGEX = / $/.freeze
|
10
|
+
|
8
11
|
class << self
|
9
12
|
# @return [Hash] locale tree
|
10
13
|
def parse(str, options)
|
11
14
|
if YAML.method(:load).arity.abs == 2
|
12
|
-
YAML.
|
15
|
+
YAML.safe_load(str, **(options || {}), permitted_classes: [Symbol], aliases: true)
|
13
16
|
else
|
14
17
|
# older jruby and rbx 2.2.7 do not accept options
|
15
18
|
YAML.load(str)
|
@@ -18,7 +21,17 @@ module I18n::Tasks
|
|
18
21
|
|
19
22
|
# @return [String]
|
20
23
|
def dump(tree, options)
|
21
|
-
tree.to_yaml(options || {})
|
24
|
+
strip_trailing_spaces(restore_emojis(tree.to_yaml(options || {})))
|
25
|
+
end
|
26
|
+
|
27
|
+
# @return [String]
|
28
|
+
def restore_emojis(yaml)
|
29
|
+
yaml.gsub(EMOJI_REGEX) { |m| [m[-8..].to_i(16)].pack('U') }
|
30
|
+
end
|
31
|
+
|
32
|
+
# @return [String]
|
33
|
+
def strip_trailing_spaces(yaml)
|
34
|
+
yaml.gsub(TRAILING_SPACE_REGEX, '')
|
22
35
|
end
|
23
36
|
end
|
24
37
|
end
|
@@ -38,7 +38,7 @@ module I18n::Tasks
|
|
38
38
|
if pattern
|
39
39
|
key_match = $~
|
40
40
|
path = format(path, locale: locale)
|
41
|
-
path.gsub!(/\\\d+/) { |m| key_match[m[1
|
41
|
+
path.gsub!(/\\\d+/) { |m| key_match[m[1..].to_i] }
|
42
42
|
(out[path] ||= Set.new) << "#{locale}.#{key}"
|
43
43
|
else
|
44
44
|
fail CommandError, "Cannot route key #{key}. Routes are #{@routes_config.inspect}"
|
@@ -168,7 +168,7 @@ module I18n::Tasks::Data::Tree
|
|
168
168
|
label = if key.nil?
|
169
169
|
Rainbow('∅').faint
|
170
170
|
else
|
171
|
-
[Rainbow(key).color(1 + level % 15),
|
171
|
+
[Rainbow(key).color(1 + (level % 15)),
|
172
172
|
(": #{format_value_for_inspect(value)}" if leaf?),
|
173
173
|
(" #{data}" if data?)].compact.join
|
174
174
|
end
|
@@ -31,13 +31,11 @@ module I18n::Tasks::Data::Tree
|
|
31
31
|
end
|
32
32
|
|
33
33
|
def to_hash(sort = false)
|
34
|
-
(@hash ||= {})[sort] ||=
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
end.map { |node| node.to_hash(sort) }.reduce({}, :deep_merge!)
|
40
|
-
end
|
34
|
+
(@hash ||= {})[sort] ||= if sort
|
35
|
+
sort_by(&:key)
|
36
|
+
else
|
37
|
+
self
|
38
|
+
end.map { |node| node.to_hash(sort) }.reduce({}, :deep_merge!)
|
41
39
|
end
|
42
40
|
|
43
41
|
delegate :to_json, to: :to_hash
|
@@ -3,7 +3,6 @@
|
|
3
3
|
require 'set'
|
4
4
|
require 'i18n/tasks/split_key'
|
5
5
|
require 'i18n/tasks/data/tree/nodes'
|
6
|
-
require 'i18n/tasks/data/tree/node'
|
7
6
|
|
8
7
|
module I18n::Tasks::Data::Tree
|
9
8
|
# Siblings represents a subtree sharing a common parent
|
@@ -50,7 +49,7 @@ module I18n::Tasks::Data::Tree
|
|
50
49
|
next
|
51
50
|
end
|
52
51
|
match = $~
|
53
|
-
new_key = to_pattern.gsub(/\\\d+/) { |m| match[m[1
|
52
|
+
new_key = to_pattern.gsub(/\\\d+/) { |m| match[m[1..].to_i] }
|
54
53
|
old_key_to_new_key[full_key] = new_key
|
55
54
|
moved_forest.merge!(Siblings.new.tap do |forest|
|
56
55
|
forest[[(node.root.try(:key) unless root), new_key].compact.join('.')] =
|
@@ -161,12 +161,12 @@ module I18n::Tasks
|
|
161
161
|
end
|
162
162
|
|
163
163
|
def grep_keys(match, opts = {})
|
164
|
-
select_keys(opts) do |full_key, _node|
|
164
|
+
select_keys(**opts) do |full_key, _node|
|
165
165
|
match === full_key # rubocop:disable Style/CaseEquality
|
166
166
|
end
|
167
167
|
end
|
168
168
|
|
169
|
-
def set_each_value!(val_pattern, key_pattern = nil, &value_proc)
|
169
|
+
def set_each_value!(val_pattern, key_pattern = nil, &value_proc) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
|
170
170
|
value_proc ||= proc do |node|
|
171
171
|
node_value = node.value
|
172
172
|
next node_value if node.reference?
|
@@ -174,15 +174,29 @@ module I18n::Tasks
|
|
174
174
|
human_key = ActiveSupport::Inflector.humanize(node.key.to_s)
|
175
175
|
full_key = node.full_key
|
176
176
|
default = (node.data[:occurrences] || []).detect { |o| o.default_arg.presence }.try(:default_arg)
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
177
|
+
if default.is_a?(Hash)
|
178
|
+
default.each_with_object({}) do |(k, v), h|
|
179
|
+
h[k] = StringInterpolation.interpolate_soft(
|
180
|
+
val_pattern,
|
181
|
+
value: node_value,
|
182
|
+
human_key: human_key,
|
183
|
+
key: full_key,
|
184
|
+
default: v,
|
185
|
+
value_or_human_key: node_value.presence || human_key,
|
186
|
+
value_or_default_or_human_key: node_value.presence || v || human_key
|
187
|
+
)
|
188
|
+
end
|
189
|
+
else
|
190
|
+
StringInterpolation.interpolate_soft(
|
191
|
+
val_pattern,
|
192
|
+
value: node_value,
|
193
|
+
human_key: human_key,
|
194
|
+
key: full_key,
|
195
|
+
default: default,
|
196
|
+
value_or_human_key: node_value.presence || human_key,
|
197
|
+
value_or_default_or_human_key: node_value.presence || default || human_key
|
198
|
+
)
|
199
|
+
end
|
186
200
|
end
|
187
201
|
pattern_re = I18n::Tasks::KeyPatternMatching.compile_key_pattern(key_pattern) if key_pattern.present?
|
188
202
|
keys.each do |key, node|
|
data/lib/i18n/tasks/html_keys.rb
CHANGED
@@ -7,8 +7,8 @@ module I18n::Tasks
|
|
7
7
|
|
8
8
|
def html_key?(full_key, locale)
|
9
9
|
!!(full_key =~ HTML_KEY_PATTERN ||
|
10
|
-
full_key =~ MAYBE_PLURAL_HTML_KEY_PATTERN &&
|
11
|
-
depluralize_key(split_key(full_key, 2)[1], locale) =~ HTML_KEY_PATTERN)
|
10
|
+
(full_key =~ MAYBE_PLURAL_HTML_KEY_PATTERN &&
|
11
|
+
depluralize_key(split_key(full_key, 2)[1], locale) =~ HTML_KEY_PATTERN))
|
12
12
|
end
|
13
13
|
end
|
14
14
|
end
|
@@ -2,22 +2,26 @@
|
|
2
2
|
|
3
3
|
module I18n::Tasks
|
4
4
|
module Interpolations
|
5
|
-
|
5
|
+
class << self
|
6
|
+
attr_accessor :variable_regex
|
7
|
+
end
|
8
|
+
@variable_regex = /%{[^}]+}/.freeze
|
6
9
|
|
7
10
|
def inconsistent_interpolations(locales: nil, base_locale: nil) # rubocop:disable Metrics/AbcSize
|
8
11
|
locales ||= self.locales
|
9
12
|
base_locale ||= self.base_locale
|
10
13
|
result = empty_forest
|
14
|
+
variable_regex = I18n::Tasks::Interpolations.variable_regex
|
11
15
|
|
12
16
|
data[base_locale].key_values.each do |key, value|
|
13
17
|
next if !value.is_a?(String) || ignore_key?(key, :inconsistent_interpolations)
|
14
18
|
|
15
|
-
base_vars = Set.new(value.scan(
|
19
|
+
base_vars = Set.new(value.scan(variable_regex))
|
16
20
|
(locales - [base_locale]).each do |current_locale|
|
17
21
|
node = data[current_locale].first.children[key]
|
18
22
|
next unless node&.value.is_a?(String)
|
19
23
|
|
20
|
-
if base_vars != Set.new(node.value.scan(
|
24
|
+
if base_vars != Set.new(node.value.scan(variable_regex))
|
21
25
|
result.merge!(node.walk_to_root.reduce(nil) { |c, p| [p.derive(children: c)] })
|
22
26
|
end
|
23
27
|
end
|
@@ -21,6 +21,7 @@ module I18n::Tasks::KeyPatternMatching
|
|
21
21
|
# In patterns:
|
22
22
|
# * is like .* in regexs
|
23
23
|
# : matches a single key
|
24
|
+
# *: matches part of a single key, equivalent to `[^.]+?` regex
|
24
25
|
# { a, b.c } match any in set, can use : and *, match is captured
|
25
26
|
def compile_key_pattern(key_pattern)
|
26
27
|
return key_pattern if key_pattern.is_a?(Regexp)
|
@@ -31,6 +32,7 @@ module I18n::Tasks::KeyPatternMatching
|
|
31
32
|
def key_pattern_re_body(key_pattern)
|
32
33
|
key_pattern
|
33
34
|
.gsub(/\./, '\.')
|
35
|
+
.gsub(/\*:/, '[^.]+?')
|
34
36
|
.gsub(/\*/, '.*')
|
35
37
|
.gsub(/:/, '(?<=^|\.)[^.]+?(?=\.|$)')
|
36
38
|
.gsub(/\{(.*?)}/) { "(#{Regexp.last_match(1).strip.gsub(/\s*,\s*/, '|')})" }
|