i18n-youdao-tasks 0.9.37
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/LICENSE.txt +22 -0
- data/README.md +448 -0
- data/Rakefile +13 -0
- data/bin/i18n-tasks +15 -0
- data/bin/i18n-tasks.cmd +2 -0
- data/config/locales/en.yml +129 -0
- data/config/locales/ru.yml +131 -0
- data/i18n-tasks.gemspec +58 -0
- data/lib/i18n/tasks/base_task.rb +52 -0
- data/lib/i18n/tasks/cli.rb +214 -0
- data/lib/i18n/tasks/command/collection.rb +21 -0
- data/lib/i18n/tasks/command/commander.rb +38 -0
- data/lib/i18n/tasks/command/commands/data.rb +107 -0
- data/lib/i18n/tasks/command/commands/eq_base.rb +22 -0
- data/lib/i18n/tasks/command/commands/health.rb +30 -0
- data/lib/i18n/tasks/command/commands/interpolations.rb +22 -0
- data/lib/i18n/tasks/command/commands/meta.rb +37 -0
- data/lib/i18n/tasks/command/commands/missing.rb +73 -0
- data/lib/i18n/tasks/command/commands/tree.rb +102 -0
- data/lib/i18n/tasks/command/commands/usages.rb +81 -0
- data/lib/i18n/tasks/command/dsl.rb +56 -0
- data/lib/i18n/tasks/command/option_parsers/enum.rb +57 -0
- data/lib/i18n/tasks/command/option_parsers/locale.rb +60 -0
- data/lib/i18n/tasks/command/options/common.rb +47 -0
- data/lib/i18n/tasks/command/options/data.rb +97 -0
- data/lib/i18n/tasks/command/options/locales.rb +44 -0
- data/lib/i18n/tasks/command_error.rb +15 -0
- data/lib/i18n/tasks/commands.rb +29 -0
- data/lib/i18n/tasks/concurrent/cache.rb +22 -0
- data/lib/i18n/tasks/concurrent/cached_value.rb +61 -0
- data/lib/i18n/tasks/configuration.rb +136 -0
- data/lib/i18n/tasks/console_context.rb +76 -0
- data/lib/i18n/tasks/data/adapter/json_adapter.rb +29 -0
- data/lib/i18n/tasks/data/adapter/yaml_adapter.rb +27 -0
- data/lib/i18n/tasks/data/file_formats.rb +99 -0
- data/lib/i18n/tasks/data/file_system.rb +14 -0
- data/lib/i18n/tasks/data/file_system_base.rb +200 -0
- data/lib/i18n/tasks/data/router/conservative_router.rb +62 -0
- data/lib/i18n/tasks/data/router/pattern_router.rb +62 -0
- data/lib/i18n/tasks/data/tree/node.rb +206 -0
- data/lib/i18n/tasks/data/tree/nodes.rb +97 -0
- data/lib/i18n/tasks/data/tree/siblings.rb +333 -0
- data/lib/i18n/tasks/data/tree/traversal.rb +197 -0
- data/lib/i18n/tasks/data.rb +87 -0
- data/lib/i18n/tasks/html_keys.rb +14 -0
- data/lib/i18n/tasks/ignore_keys.rb +31 -0
- data/lib/i18n/tasks/interpolations.rb +30 -0
- data/lib/i18n/tasks/key_pattern_matching.rb +38 -0
- data/lib/i18n/tasks/locale_list.rb +19 -0
- data/lib/i18n/tasks/locale_pathname.rb +17 -0
- data/lib/i18n/tasks/logging.rb +35 -0
- data/lib/i18n/tasks/missing_keys.rb +185 -0
- data/lib/i18n/tasks/plural_keys.rb +67 -0
- data/lib/i18n/tasks/references.rb +103 -0
- data/lib/i18n/tasks/reports/base.rb +75 -0
- data/lib/i18n/tasks/reports/terminal.rb +243 -0
- data/lib/i18n/tasks/scanners/erb_ast_processor.rb +51 -0
- data/lib/i18n/tasks/scanners/erb_ast_scanner.rb +48 -0
- data/lib/i18n/tasks/scanners/file_scanner.rb +66 -0
- data/lib/i18n/tasks/scanners/files/caching_file_finder.rb +35 -0
- data/lib/i18n/tasks/scanners/files/caching_file_finder_provider.rb +31 -0
- data/lib/i18n/tasks/scanners/files/caching_file_reader.rb +28 -0
- data/lib/i18n/tasks/scanners/files/file_finder.rb +61 -0
- data/lib/i18n/tasks/scanners/files/file_reader.rb +19 -0
- data/lib/i18n/tasks/scanners/local_ruby_parser.rb +74 -0
- data/lib/i18n/tasks/scanners/occurrence_from_position.rb +29 -0
- data/lib/i18n/tasks/scanners/pattern_mapper.rb +60 -0
- data/lib/i18n/tasks/scanners/pattern_scanner.rb +108 -0
- data/lib/i18n/tasks/scanners/pattern_with_scope_scanner.rb +100 -0
- data/lib/i18n/tasks/scanners/relative_keys.rb +70 -0
- data/lib/i18n/tasks/scanners/results/key_occurrences.rb +54 -0
- data/lib/i18n/tasks/scanners/results/occurrence.rb +69 -0
- data/lib/i18n/tasks/scanners/ruby_ast_call_finder.rb +63 -0
- data/lib/i18n/tasks/scanners/ruby_ast_scanner.rb +234 -0
- data/lib/i18n/tasks/scanners/ruby_key_literals.rb +30 -0
- data/lib/i18n/tasks/scanners/scanner.rb +17 -0
- data/lib/i18n/tasks/scanners/scanner_multiplexer.rb +43 -0
- data/lib/i18n/tasks/split_key.rb +72 -0
- data/lib/i18n/tasks/stats.rb +24 -0
- data/lib/i18n/tasks/string_interpolation.rb +17 -0
- data/lib/i18n/tasks/translation.rb +29 -0
- data/lib/i18n/tasks/translators/base_translator.rb +156 -0
- data/lib/i18n/tasks/translators/deepl_translator.rb +81 -0
- data/lib/i18n/tasks/translators/google_translator.rb +69 -0
- data/lib/i18n/tasks/translators/yandex_translator.rb +63 -0
- data/lib/i18n/tasks/translators/youdao_translator.rb +69 -0
- data/lib/i18n/tasks/unused_keys.rb +25 -0
- data/lib/i18n/tasks/used_keys.rb +184 -0
- data/lib/i18n/tasks/version.rb +7 -0
- data/lib/i18n/tasks.rb +69 -0
- data/templates/config/i18n-tasks.yml +142 -0
- data/templates/minitest/i18n_test.rb +36 -0
- data/templates/rspec/i18n_spec.rb +34 -0
- metadata +441 -0
@@ -0,0 +1,22 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module I18n::Tasks
|
4
|
+
module Command
|
5
|
+
module Commands
|
6
|
+
module Interpolations
|
7
|
+
include Command::Collection
|
8
|
+
|
9
|
+
cmd :check_consistent_interpolations,
|
10
|
+
pos: '[locale ...]',
|
11
|
+
desc: t('i18n_tasks.cmd.desc.check_consistent_interpolations'),
|
12
|
+
args: %i[locales out_format]
|
13
|
+
|
14
|
+
def check_consistent_interpolations(opt = {})
|
15
|
+
forest = i18n.inconsistent_interpolations(**opt.slice(:locales, :base_locale))
|
16
|
+
print_forest forest, opt, :inconsistent_interpolations
|
17
|
+
:exit1 unless forest.empty?
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module I18n::Tasks
|
4
|
+
module Command
|
5
|
+
module Commands
|
6
|
+
module Meta
|
7
|
+
include Command::Collection
|
8
|
+
|
9
|
+
cmd :config,
|
10
|
+
pos: '[section ...]',
|
11
|
+
desc: t('i18n_tasks.cmd.desc.config')
|
12
|
+
|
13
|
+
def config(opts = {})
|
14
|
+
cfg = i18n.config_for_inspect
|
15
|
+
cfg = cfg.slice(*opts[:arguments]) if opts[:arguments].present?
|
16
|
+
cfg = cfg.to_yaml
|
17
|
+
cfg.sub!(/\A---\n/, '')
|
18
|
+
cfg.gsub!(/^([^\s-].+?:)/, Rainbow('\1').cyan.bright)
|
19
|
+
puts cfg
|
20
|
+
end
|
21
|
+
|
22
|
+
cmd :gem_path, desc: t('i18n_tasks.cmd.desc.gem_path')
|
23
|
+
|
24
|
+
def gem_path
|
25
|
+
puts I18n::Tasks.gem_path
|
26
|
+
end
|
27
|
+
|
28
|
+
cmd :irb, desc: t('i18n_tasks.cmd.desc.irb')
|
29
|
+
|
30
|
+
def irb
|
31
|
+
require 'i18n/tasks/console_context'
|
32
|
+
::I18n::Tasks::ConsoleContext.start
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'i18n/tasks/command/collection'
|
4
|
+
|
5
|
+
module I18n::Tasks
|
6
|
+
module Command
|
7
|
+
module Commands
|
8
|
+
module Missing
|
9
|
+
include Command::Collection
|
10
|
+
|
11
|
+
missing_types = I18n::Tasks::MissingKeys.missing_keys_types
|
12
|
+
arg :missing_types,
|
13
|
+
'-t',
|
14
|
+
"--types #{missing_types * ','}",
|
15
|
+
Array,
|
16
|
+
t('i18n_tasks.cmd.args.desc.missing_types', valid: missing_types * ', '),
|
17
|
+
parser: OptionParsers::Enum::ListParser.new(
|
18
|
+
missing_types,
|
19
|
+
proc do |invalid, valid|
|
20
|
+
I18n.t('i18n_tasks.cmd.errors.invalid_missing_type',
|
21
|
+
invalid: invalid * ', ', valid: valid * ', ', count: invalid.length)
|
22
|
+
end
|
23
|
+
)
|
24
|
+
|
25
|
+
cmd :missing,
|
26
|
+
pos: '[locale ...]',
|
27
|
+
desc: t('i18n_tasks.cmd.desc.missing'),
|
28
|
+
args: %i[locales out_format missing_types]
|
29
|
+
|
30
|
+
def missing(opt = {})
|
31
|
+
forest = i18n.missing_keys(**opt.slice(:locales, :base_locale, :types))
|
32
|
+
print_forest forest, opt, :missing_keys
|
33
|
+
:exit1 unless forest.empty?
|
34
|
+
end
|
35
|
+
|
36
|
+
cmd :translate_missing,
|
37
|
+
pos: '[locale ...]',
|
38
|
+
desc: t('i18n_tasks.cmd.desc.translate_missing'),
|
39
|
+
args: [:locales, :locale_to_translate_from, arg(:out_format).from(1), :translation_backend]
|
40
|
+
|
41
|
+
def translate_missing(opt = {})
|
42
|
+
missing = i18n.missing_diff_forest opt[:locales], opt[:from]
|
43
|
+
translated = i18n.translate_forest missing, from: opt[:from], backend: opt[:backend].to_sym
|
44
|
+
i18n.data.merge! translated
|
45
|
+
log_stderr t('i18n_tasks.translate_missing.translated', count: translated.leaves.count)
|
46
|
+
print_forest translated, opt
|
47
|
+
end
|
48
|
+
|
49
|
+
cmd :add_missing,
|
50
|
+
pos: '[locale ...]',
|
51
|
+
desc: t('i18n_tasks.cmd.desc.add_missing'),
|
52
|
+
args: [:locales, :out_format, arg(:value) + [{ default: '%{value_or_default_or_human_key}' }],
|
53
|
+
['--nil-value', 'Set value to nil. Takes precedence over the value argument.']]
|
54
|
+
|
55
|
+
# Merge base locale first, as this may affect the value for the other locales
|
56
|
+
def add_missing(opt = {})
|
57
|
+
[
|
58
|
+
[i18n.base_locale] & opt[:locales],
|
59
|
+
opt[:locales] - [i18n.base_locale]
|
60
|
+
].reject(&:empty?).each_with_object(i18n.empty_forest) do |locales, added|
|
61
|
+
forest = i18n.missing_keys(locales: locales, **opt.slice(:types, :base_locale))
|
62
|
+
.set_each_value!(opt[:'nil-value'] ? nil : opt[:value])
|
63
|
+
i18n.data.merge! forest
|
64
|
+
added.merge! forest
|
65
|
+
end.tap do |added|
|
66
|
+
log_stderr t('i18n_tasks.add_missing.added', count: added.leaves.count)
|
67
|
+
print_forest added, opt
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
@@ -0,0 +1,102 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module I18n::Tasks
|
4
|
+
module Command
|
5
|
+
module Commands
|
6
|
+
module Tree
|
7
|
+
include Command::Collection
|
8
|
+
include I18n::Tasks::KeyPatternMatching
|
9
|
+
|
10
|
+
cmd :tree_translate,
|
11
|
+
pos: '[tree (or stdin)]',
|
12
|
+
desc: t('i18n_tasks.cmd.desc.tree_translate'),
|
13
|
+
args: [:locale_to_translate_from, arg(:data_format).from(1), :translation_backend]
|
14
|
+
|
15
|
+
def tree_translate(opts = {})
|
16
|
+
forest = forest_pos_or_stdin!(opts)
|
17
|
+
print_forest i18n.translate_forest(forest, from: opts[:from], backend: opts[:backend].to_sym), opts
|
18
|
+
end
|
19
|
+
|
20
|
+
cmd :tree_merge,
|
21
|
+
pos: '[[tree] [tree] ... (or stdin)]',
|
22
|
+
desc: t('i18n_tasks.cmd.desc.tree_merge'),
|
23
|
+
args: %i[data_format nostdin]
|
24
|
+
|
25
|
+
def tree_merge(opts = {})
|
26
|
+
print_forest merge_forests_stdin_and_pos!(opts), opts
|
27
|
+
end
|
28
|
+
|
29
|
+
cmd :tree_filter,
|
30
|
+
pos: '[pattern] [tree (or stdin)]',
|
31
|
+
desc: t('i18n_tasks.cmd.desc.tree_filter'),
|
32
|
+
args: %i[data_format pattern]
|
33
|
+
|
34
|
+
def tree_filter(opts = {})
|
35
|
+
pattern = arg_or_pos! :pattern, opts
|
36
|
+
forest = forest_pos_or_stdin! opts
|
37
|
+
unless pattern.blank?
|
38
|
+
pattern_re = i18n.compile_key_pattern(pattern)
|
39
|
+
forest = forest.select_keys { |full_key, _node| full_key =~ pattern_re }
|
40
|
+
end
|
41
|
+
print_forest forest, opts
|
42
|
+
end
|
43
|
+
|
44
|
+
arg :all_locales,
|
45
|
+
'-a',
|
46
|
+
'--all-locales',
|
47
|
+
t('i18n_tasks.cmd.args.desc.all_locales')
|
48
|
+
|
49
|
+
cmd :tree_mv,
|
50
|
+
pos: 'FROM_KEY_PATTERN TO_KEY_PATTERN [tree (or stdin)]',
|
51
|
+
desc: t('i18n_tasks.cmd.desc.tree_mv_key'),
|
52
|
+
args: %i[data_format all_locales]
|
53
|
+
def tree_mv(opt = {})
|
54
|
+
fail CommandError, 'requires FROM_KEY_PATTERN and TO_KEY_PATTERN' if opt[:arguments].size < 2
|
55
|
+
|
56
|
+
from_pattern = opt[:arguments].shift
|
57
|
+
to_pattern = opt[:arguments].shift
|
58
|
+
forest = forest_pos_or_stdin!(opt)
|
59
|
+
forest.mv_key!(compile_key_pattern(from_pattern), to_pattern, root: !opt[:'all-locales'])
|
60
|
+
print_forest forest, opt
|
61
|
+
end
|
62
|
+
|
63
|
+
cmd :tree_subtract,
|
64
|
+
pos: '[[tree] [tree] ... (or stdin)]',
|
65
|
+
desc: t('i18n_tasks.cmd.desc.tree_subtract'),
|
66
|
+
args: %i[data_format nostdin]
|
67
|
+
|
68
|
+
def tree_subtract(opt = {})
|
69
|
+
forests = forests_stdin_and_pos! opt, 2
|
70
|
+
forest = forests.reduce(:subtract_by_key) || empty_forest
|
71
|
+
print_forest forest, opt
|
72
|
+
end
|
73
|
+
|
74
|
+
cmd :tree_set_value,
|
75
|
+
pos: '[VALUE] [tree (or stdin)]',
|
76
|
+
desc: t('i18n_tasks.cmd.desc.tree_set_value'),
|
77
|
+
args: %i[value data_format nostdin pattern]
|
78
|
+
|
79
|
+
def tree_set_value(opt = {})
|
80
|
+
value = arg_or_pos! :value, opt
|
81
|
+
forest = forest_pos_or_stdin!(opt)
|
82
|
+
key_pattern = opt[:pattern]
|
83
|
+
fail CommandError, 'pass value (-v, --value)' if value.blank?
|
84
|
+
|
85
|
+
forest.set_each_value!(value, key_pattern)
|
86
|
+
print_forest forest, opt
|
87
|
+
end
|
88
|
+
|
89
|
+
cmd :tree_convert,
|
90
|
+
pos: '[tree (or stdin)]',
|
91
|
+
desc: t('i18n_tasks.cmd.desc.tree_convert'),
|
92
|
+
args: [arg(:data_format).dup.tap { |a| a[0..1] = ['-f', '--from FORMAT'] },
|
93
|
+
arg(:out_format).dup.tap { |a| a[0..1] = ['-t', '--to FORMAT'] }]
|
94
|
+
|
95
|
+
def tree_convert(opt = {})
|
96
|
+
forest = forest_pos_or_stdin! opt.merge(format: opt[:from])
|
97
|
+
print_forest forest, opt.merge(format: opt[:to])
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
@@ -0,0 +1,81 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module I18n::Tasks
|
4
|
+
module Command
|
5
|
+
module Commands
|
6
|
+
module Usages
|
7
|
+
include Command::Collection
|
8
|
+
|
9
|
+
arg :strict,
|
10
|
+
'--[no-]strict',
|
11
|
+
t('i18n_tasks.cmd.args.desc.strict')
|
12
|
+
|
13
|
+
arg :keep_order,
|
14
|
+
'-k',
|
15
|
+
'--keep-order',
|
16
|
+
t('i18n_tasks.cmd.args.desc.keep_order')
|
17
|
+
|
18
|
+
cmd :find,
|
19
|
+
pos: '[pattern]',
|
20
|
+
desc: t('i18n_tasks.cmd.desc.find'),
|
21
|
+
args: %i[out_format pattern strict]
|
22
|
+
|
23
|
+
def find(opt = {})
|
24
|
+
opt[:filter] ||= opt.delete(:pattern) || opt[:arguments].try(:first)
|
25
|
+
result = i18n.used_tree(strict: opt[:strict], key_filter: opt[:filter].presence, include_raw_references: true)
|
26
|
+
print_forest result, opt, :used_keys
|
27
|
+
end
|
28
|
+
|
29
|
+
cmd :unused,
|
30
|
+
pos: '[locale ...]',
|
31
|
+
desc: t('i18n_tasks.cmd.desc.unused'),
|
32
|
+
args: %i[locales out_format strict]
|
33
|
+
|
34
|
+
def unused(opt = {})
|
35
|
+
forest = i18n.unused_keys(**opt.slice(:locales, :strict))
|
36
|
+
print_forest forest, opt, :unused_keys
|
37
|
+
:exit1 unless forest.empty?
|
38
|
+
end
|
39
|
+
|
40
|
+
cmd :remove_unused,
|
41
|
+
pos: '[locale ...]',
|
42
|
+
desc: t('i18n_tasks.cmd.desc.remove_unused'),
|
43
|
+
args: %i[locales out_format strict keep_order confirm pattern]
|
44
|
+
|
45
|
+
def remove_unused(opt = {}) # rubocop:disable Metrics/AbcSize
|
46
|
+
unused_keys = i18n.unused_keys(**opt.slice(:locales, :strict))
|
47
|
+
|
48
|
+
if opt[:pattern]
|
49
|
+
pattern_re = i18n.compile_key_pattern(opt[:pattern])
|
50
|
+
unused_keys = unused_keys.select_keys { |full_key, _node| full_key =~ pattern_re }
|
51
|
+
end
|
52
|
+
|
53
|
+
if unused_keys.present?
|
54
|
+
terminal_report.unused_keys(unused_keys)
|
55
|
+
confirm_remove_unused!(unused_keys, opt)
|
56
|
+
i18n.data.config = i18n.data.config.merge(sort: false) if opt[:'keep-order']
|
57
|
+
removed = i18n.data.remove_by_key!(unused_keys)
|
58
|
+
log_stderr t('i18n_tasks.remove_unused.removed', count: unused_keys.leaves.count)
|
59
|
+
print_forest removed, opt
|
60
|
+
else
|
61
|
+
log_stderr Rainbow(t('i18n_tasks.remove_unused.noop')).green.bright
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
private
|
66
|
+
|
67
|
+
def confirm_remove_unused!(unused_keys, opt)
|
68
|
+
return if ENV['CONFIRM'] || opt[:confirm]
|
69
|
+
|
70
|
+
locales = Rainbow(unused_keys.flat_map { |root| root.key.split('+') }.sort.uniq * ', ').bright
|
71
|
+
msg = [
|
72
|
+
Rainbow(t('i18n_tasks.remove_unused.confirm', count: unused_keys.leaves.count, locales: locales)).red,
|
73
|
+
Rainbow(t('i18n_tasks.common.continue_q')).yellow,
|
74
|
+
Rainbow('(yes/no)').yellow
|
75
|
+
].join(' ')
|
76
|
+
exit 1 unless agree msg
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module I18n::Tasks
|
4
|
+
module Command
|
5
|
+
module DSL
|
6
|
+
def self.included(base)
|
7
|
+
base.module_eval do
|
8
|
+
@dsl = Hash.new { |h, k| h[k] = {} }
|
9
|
+
extend ClassMethods
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
def t(*args, **kwargs)
|
14
|
+
I18n.t(*args, **kwargs)
|
15
|
+
end
|
16
|
+
|
17
|
+
module ClassMethods
|
18
|
+
def cmd(name, conf = nil)
|
19
|
+
if conf
|
20
|
+
conf = conf.dup
|
21
|
+
conf[:args] = (conf[:args] || []).map { |arg| arg.is_a?(Symbol) ? arg(arg) : arg }
|
22
|
+
dsl(:cmds)[name] = conf
|
23
|
+
else
|
24
|
+
dsl(:cmds)[name]
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def arg(ref, *args)
|
29
|
+
if args.present?
|
30
|
+
dsl(:args)[ref] = args
|
31
|
+
else
|
32
|
+
dsl(:args)[ref]
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def cmds
|
37
|
+
dsl(:cmds)
|
38
|
+
end
|
39
|
+
|
40
|
+
def dsl(key)
|
41
|
+
@dsl[key]
|
42
|
+
end
|
43
|
+
|
44
|
+
# late-bound I18n.t for module bodies
|
45
|
+
def t(*args, **kwargs)
|
46
|
+
proc { I18n.t(*args, **kwargs) }
|
47
|
+
end
|
48
|
+
|
49
|
+
# if class is a module, merge DSL definitions when it is included
|
50
|
+
def included(base)
|
51
|
+
base.instance_variable_get(:@dsl).deep_merge!(@dsl)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module I18n::Tasks
|
4
|
+
module Command
|
5
|
+
module OptionParsers
|
6
|
+
module Enum
|
7
|
+
class Parser
|
8
|
+
DEFAULT_ERROR = proc do |invalid, valid|
|
9
|
+
I18n.t('i18n_tasks.cmd.enum_opt.invalid', invalid: invalid, valid: valid * ', ')
|
10
|
+
end
|
11
|
+
|
12
|
+
def initialize(valid, error_message = DEFAULT_ERROR)
|
13
|
+
@valid = valid.map(&:to_s)
|
14
|
+
@error_message = error_message
|
15
|
+
end
|
16
|
+
|
17
|
+
def call(value, *)
|
18
|
+
return @valid.first unless value.present?
|
19
|
+
|
20
|
+
if @valid.include?(value)
|
21
|
+
value
|
22
|
+
else
|
23
|
+
fail CommandError, @error_message.call(value, @valid)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
class ListParser
|
29
|
+
DEFAULT_ERROR = proc do |invalid, valid|
|
30
|
+
I18n.t('i18n_tasks.cmd.enum_list_opt.invalid', invalid: invalid * ', ', valid: valid * ', ')
|
31
|
+
end
|
32
|
+
|
33
|
+
def initialize(valid, error_message = DEFAULT_ERROR)
|
34
|
+
@valid = valid.map(&:to_s)
|
35
|
+
@error_message = error_message
|
36
|
+
end
|
37
|
+
|
38
|
+
def call(values, *)
|
39
|
+
values = Array(values)
|
40
|
+
return @valid if values == %w[all]
|
41
|
+
|
42
|
+
invalid = values - @valid
|
43
|
+
if invalid.empty?
|
44
|
+
if values.empty?
|
45
|
+
@valid
|
46
|
+
else
|
47
|
+
values
|
48
|
+
end
|
49
|
+
else
|
50
|
+
fail CommandError, @error_message.call(invalid, @valid)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module I18n::Tasks
|
4
|
+
module Command
|
5
|
+
module OptionParsers
|
6
|
+
module Locale
|
7
|
+
module Validator
|
8
|
+
VALID_LOCALE_RE = /\A\w[\w\-.]*\z/i.freeze
|
9
|
+
|
10
|
+
def validate!(locale)
|
11
|
+
if VALID_LOCALE_RE !~ locale
|
12
|
+
fail CommandError, I18n.t('i18n_tasks.cmd.errors.invalid_locale', invalid: locale)
|
13
|
+
end
|
14
|
+
|
15
|
+
locale
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
module Parser
|
20
|
+
module_function
|
21
|
+
|
22
|
+
extend Validator
|
23
|
+
|
24
|
+
# @param [#base_locale, #locales] context
|
25
|
+
def call(val, context)
|
26
|
+
if val.blank? || val == 'base'
|
27
|
+
context.base_locale
|
28
|
+
else
|
29
|
+
validate! val
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
module ListParser
|
35
|
+
module_function
|
36
|
+
|
37
|
+
extend Validator
|
38
|
+
|
39
|
+
# @param [#base_locale,#locales] context
|
40
|
+
def call(vals, context)
|
41
|
+
if vals == %w[all] || vals.blank?
|
42
|
+
context.locales
|
43
|
+
else
|
44
|
+
move_base_locale_to_front!(vals.map { |v| v == 'base' ? context.base_locale : v }, context.base_locale)
|
45
|
+
end.tap do |locales|
|
46
|
+
locales.each { |locale| validate! locale }
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def move_base_locale_to_front!(locales, base_locale)
|
51
|
+
if (pos = locales.index(base_locale)) && pos.positive?
|
52
|
+
locales[pos], locales[0] = locales[0], locales[pos]
|
53
|
+
end
|
54
|
+
locales
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'i18n/tasks/command/dsl'
|
4
|
+
|
5
|
+
module I18n::Tasks
|
6
|
+
module Command
|
7
|
+
module Options
|
8
|
+
module Common
|
9
|
+
include Command::DSL
|
10
|
+
|
11
|
+
arg :nostdin,
|
12
|
+
'-S',
|
13
|
+
'--nostdin',
|
14
|
+
t('i18n_tasks.cmd.args.desc.nostdin')
|
15
|
+
|
16
|
+
arg :confirm,
|
17
|
+
'-y',
|
18
|
+
'--confirm',
|
19
|
+
desc: t('i18n_tasks.cmd.args.desc.confirm')
|
20
|
+
|
21
|
+
arg :pattern,
|
22
|
+
'-p',
|
23
|
+
'--pattern PATTERN',
|
24
|
+
t('i18n_tasks.cmd.args.desc.key_pattern')
|
25
|
+
|
26
|
+
arg :value,
|
27
|
+
'-v',
|
28
|
+
'--value VALUE',
|
29
|
+
t('i18n_tasks.cmd.args.desc.value')
|
30
|
+
|
31
|
+
arg :config,
|
32
|
+
'-c',
|
33
|
+
'--config FILE',
|
34
|
+
t('i18n_tasks.cmd.args.desc.config')
|
35
|
+
|
36
|
+
|
37
|
+
def arg_or_pos!(key, opts)
|
38
|
+
opts[key] ||= opts[:arguments].try(:shift)
|
39
|
+
end
|
40
|
+
|
41
|
+
def pos_or_stdin!(opts)
|
42
|
+
opts[:arguments].try(:shift) || $stdin.read
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,97 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'i18n/tasks/command/option_parsers/enum'
|
4
|
+
|
5
|
+
module I18n::Tasks
|
6
|
+
module Command
|
7
|
+
module Options
|
8
|
+
module Data
|
9
|
+
include Command::DSL
|
10
|
+
|
11
|
+
DATA_FORMATS = %w[yaml json keys].freeze
|
12
|
+
OUT_FORMATS = ['terminal-table', *DATA_FORMATS, 'inspect', 'key-values'].freeze
|
13
|
+
|
14
|
+
format_arg = proc do |type, values|
|
15
|
+
default = values.first
|
16
|
+
arg type,
|
17
|
+
'-f',
|
18
|
+
'--format FORMAT',
|
19
|
+
values,
|
20
|
+
{ 'yml' => 'yaml' },
|
21
|
+
t("i18n_tasks.cmd.args.desc.#{type}", valid_text: values * ', ', default_text: default),
|
22
|
+
parser: OptionParsers::Enum::Parser.new(values,
|
23
|
+
proc do |value, valid|
|
24
|
+
I18n.t('i18n_tasks.cmd.errors.invalid_format',
|
25
|
+
invalid: value, valid: valid * ', ')
|
26
|
+
end)
|
27
|
+
end
|
28
|
+
|
29
|
+
# i18n-tasks-use t('i18n_tasks.cmd.args.desc.data_format')
|
30
|
+
format_arg.call(:data_format, DATA_FORMATS)
|
31
|
+
|
32
|
+
# i18n-tasks-use t('i18n_tasks.cmd.args.desc.out_format')
|
33
|
+
format_arg.call(:out_format, OUT_FORMATS)
|
34
|
+
|
35
|
+
# @return [I18n::Tasks::Data::Tree::Siblings]
|
36
|
+
def forest_pos_or_stdin!(opt, format = opt[:format])
|
37
|
+
parse_forest(pos_or_stdin!(opt), format)
|
38
|
+
end
|
39
|
+
|
40
|
+
# @return [Array<I18n::Tasks::Data::Tree::Siblings>] trees read from stdin and positional arguments
|
41
|
+
def forests_stdin_and_pos!(opt, num = false, format = opt[:format])
|
42
|
+
args = opt[:arguments] || []
|
43
|
+
if opt[:nostdin]
|
44
|
+
sources = []
|
45
|
+
else
|
46
|
+
sources = [$stdin.read]
|
47
|
+
num -= 1 if num
|
48
|
+
end
|
49
|
+
if num
|
50
|
+
num.times { sources << args.shift }
|
51
|
+
else
|
52
|
+
sources += args
|
53
|
+
args.clear
|
54
|
+
end
|
55
|
+
sources.map { |src| parse_forest(src, format) }
|
56
|
+
end
|
57
|
+
|
58
|
+
def merge_forests_stdin_and_pos!(opt)
|
59
|
+
forests_stdin_and_pos!(opt).inject(i18n.empty_forest) do |result, forest|
|
60
|
+
result.merge! forest
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
# @return [I18n::Tasks::Data::Tree::Siblings]
|
65
|
+
def parse_forest(src, format)
|
66
|
+
fail CommandError, I18n.t('i18n_tasks.cmd.errors.pass_forest') unless src
|
67
|
+
|
68
|
+
if format == 'keys'
|
69
|
+
::I18n::Tasks::Data::Tree::Siblings.from_key_names parse_keys(src)
|
70
|
+
else
|
71
|
+
::I18n::Tasks::Data::Tree::Siblings.from_nested_hash i18n.data.adapter_parse(src, format)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def parse_keys(src)
|
76
|
+
Array(src).compact.flat_map { |v| v.strip.split(/\s*[,\s\n]\s*/) }.map(&:presence).compact
|
77
|
+
end
|
78
|
+
|
79
|
+
def print_forest(forest, opts, version = :show_tree)
|
80
|
+
format = opts[:format].to_s
|
81
|
+
case format
|
82
|
+
when 'terminal-table'
|
83
|
+
terminal_report.send(version, forest)
|
84
|
+
when 'inspect'
|
85
|
+
puts forest.inspect
|
86
|
+
when 'keys'
|
87
|
+
puts forest.key_names(root: true)
|
88
|
+
when 'key-values'
|
89
|
+
puts forest.key_values(root: true).map { |kv| kv.join("\t") }
|
90
|
+
when *DATA_FORMATS
|
91
|
+
puts i18n.data.adapter_dump forest.to_hash(true), format
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'i18n/tasks/command/option_parsers/locale'
|
4
|
+
|
5
|
+
module I18n::Tasks
|
6
|
+
module Command
|
7
|
+
module Options
|
8
|
+
module Locales
|
9
|
+
include Command::DSL
|
10
|
+
|
11
|
+
arg :locales,
|
12
|
+
'-l',
|
13
|
+
'--locales en,es,ru',
|
14
|
+
Array,
|
15
|
+
t('i18n_tasks.cmd.args.desc.locales_filter'),
|
16
|
+
parser: OptionParsers::Locale::ListParser,
|
17
|
+
default: 'all',
|
18
|
+
consume_positional: true
|
19
|
+
|
20
|
+
arg :locale,
|
21
|
+
'-l',
|
22
|
+
'--locale en',
|
23
|
+
t('i18n_tasks.cmd.args.desc.locale'),
|
24
|
+
parser: OptionParsers::Locale::Parser,
|
25
|
+
default: 'base'
|
26
|
+
|
27
|
+
arg :locale_to_translate_from,
|
28
|
+
'-f',
|
29
|
+
'--from en',
|
30
|
+
t('i18n_tasks.cmd.args.desc.locale_to_translate_from'),
|
31
|
+
parser: OptionParsers::Locale::Parser,
|
32
|
+
default: 'base'
|
33
|
+
|
34
|
+
TRANSLATION_BACKENDS = %w[google deepl].freeze
|
35
|
+
arg :translation_backend,
|
36
|
+
'-b',
|
37
|
+
'--backend BACKEND',
|
38
|
+
t('i18n_tasks.cmd.args.desc.translation_backend'),
|
39
|
+
parser: OptionParsers::Locale::Parser,
|
40
|
+
default: TRANSLATION_BACKENDS[0]
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|