i18n-tasks 0.6.3 → 0.7.0
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/.travis.yml +3 -0
- data/Gemfile +2 -1
- data/README.md +80 -78
- data/bin/i18n-tasks +24 -30
- data/config/i18n-tasks.yml +87 -0
- data/config/locales/en.yml +95 -0
- data/i18n-tasks.gemspec +1 -0
- data/lib/i18n/tasks.rb +10 -0
- data/lib/i18n/tasks/base_task.rb +6 -2
- data/lib/i18n/tasks/command/collection.rb +18 -0
- data/lib/i18n/tasks/command/commander.rb +72 -0
- data/lib/i18n/tasks/command/commands/data.rb +73 -0
- data/lib/i18n/tasks/command/commands/eq_base.rb +20 -0
- data/lib/i18n/tasks/command/commands/health.rb +26 -0
- data/lib/i18n/tasks/command/commands/meta.rb +35 -0
- data/lib/i18n/tasks/command/commands/missing.rb +73 -0
- data/lib/i18n/tasks/command/commands/tree.rb +92 -0
- data/lib/i18n/tasks/command/commands/usages.rb +70 -0
- data/lib/i18n/tasks/command/commands/xlsx.rb +27 -0
- data/lib/i18n/tasks/command/dsl.rb +27 -0
- data/lib/i18n/tasks/command/dsl/cmd.rb +19 -0
- data/lib/i18n/tasks/command/dsl/cmd_opt.rb +19 -0
- data/lib/i18n/tasks/command/dsl/enum_opt.rb +26 -0
- data/lib/i18n/tasks/command/options/common.rb +48 -0
- data/lib/i18n/tasks/command/options/enum_opt.rb +44 -0
- data/lib/i18n/tasks/command/options/list_opt.rb +11 -0
- data/lib/i18n/tasks/command/options/locales.rb +47 -0
- data/lib/i18n/tasks/command/options/trees.rb +101 -0
- data/lib/i18n/tasks/command_error.rb +3 -0
- data/lib/i18n/tasks/commands.rb +22 -169
- data/lib/i18n/tasks/configuration.rb +1 -16
- data/lib/i18n/tasks/console_context.rb +1 -1
- data/lib/i18n/tasks/data.rb +13 -8
- data/lib/i18n/tasks/data/file_formats.rb +29 -18
- data/lib/i18n/tasks/data/file_system_base.rb +35 -4
- data/lib/i18n/tasks/data/router/conservative_router.rb +18 -11
- data/lib/i18n/tasks/data/tree/node.rb +5 -15
- data/lib/i18n/tasks/data/tree/nodes.rb +0 -3
- data/lib/i18n/tasks/data/tree/siblings.rb +32 -2
- data/lib/i18n/tasks/data/tree/traversal.rb +117 -96
- data/lib/i18n/tasks/google_translation.rb +25 -25
- data/lib/i18n/tasks/html_keys.rb +10 -0
- data/lib/i18n/tasks/key_pattern_matching.rb +1 -0
- data/lib/i18n/tasks/locale_list.rb +19 -0
- data/lib/i18n/tasks/missing_keys.rb +32 -33
- data/lib/i18n/tasks/plural_keys.rb +1 -1
- data/lib/i18n/tasks/reports/base.rb +4 -9
- data/lib/i18n/tasks/reports/spreadsheet.rb +5 -5
- data/lib/i18n/tasks/reports/terminal.rb +62 -38
- data/lib/i18n/tasks/scanners/base_scanner.rb +5 -4
- data/lib/i18n/tasks/slop_command.rb +27 -0
- data/lib/i18n/tasks/stats.rb +20 -0
- data/lib/i18n/tasks/string_interpolation.rb +14 -0
- data/lib/i18n/tasks/unused_keys.rb +0 -10
- data/lib/i18n/tasks/version.rb +1 -1
- data/spec/commands/data_commands_spec.rb +38 -0
- data/spec/commands/tree_commands_spec.rb +68 -0
- data/spec/fixtures/app/views/index.html.slim +1 -0
- data/spec/google_translate_spec.rb +5 -3
- data/spec/i18n_spec.rb +18 -0
- data/spec/i18n_tasks_spec.rb +8 -8
- data/spec/spec_helper.rb +3 -3
- data/spec/support/test_codebase.rb +4 -1
- data/spec/used_keys_spec.rb +7 -7
- data/templates/config/i18n-tasks.yml +2 -2
- metadata +48 -4
- data/lib/i18n/tasks/commands_base.rb +0 -107
- data/lib/i18n/tasks/fill_tasks.rb +0 -40
@@ -0,0 +1,70 @@
|
|
1
|
+
module I18n::Tasks
|
2
|
+
module Command
|
3
|
+
module Commands
|
4
|
+
module Usages
|
5
|
+
include Command::Collection
|
6
|
+
|
7
|
+
cmd_opt :strict, {
|
8
|
+
short: :s,
|
9
|
+
long: :strict,
|
10
|
+
desc: I18n.t('i18n_tasks.cmd.args.desc.strict')
|
11
|
+
}
|
12
|
+
|
13
|
+
cmd :find,
|
14
|
+
args: '[pattern]',
|
15
|
+
desc: I18n.t('i18n_tasks.cmd.desc.find'),
|
16
|
+
opt: cmd_opts(:out_format, :pattern)
|
17
|
+
|
18
|
+
def find(opt = {})
|
19
|
+
opt_output_format! opt
|
20
|
+
opt[:filter] ||= opt.delete(:pattern) || opt[:arguments].try(:first)
|
21
|
+
print_forest i18n.used_tree(key_filter: opt[:filter].presence, source_occurrences: true), opt, :used_keys
|
22
|
+
end
|
23
|
+
|
24
|
+
cmd :unused,
|
25
|
+
args: '[locale ...]',
|
26
|
+
desc: I18n.t('i18n_tasks.cmd.desc.unused'),
|
27
|
+
opt: cmd_opts(:locales, :out_format, :strict)
|
28
|
+
|
29
|
+
def unused(opt = {})
|
30
|
+
opt_locales! opt
|
31
|
+
opt_output_format! opt
|
32
|
+
print_forest i18n.unused_keys(opt), opt, :unused_keys
|
33
|
+
end
|
34
|
+
|
35
|
+
cmd :remove_unused,
|
36
|
+
args: '[locale ...]',
|
37
|
+
desc: I18n.t('i18n_tasks.cmd.desc.remove_unused'),
|
38
|
+
opt: cmd_opts(:locales, :out_format, :strict, :confirm)
|
39
|
+
|
40
|
+
def remove_unused(opt = {})
|
41
|
+
opt_locales! opt
|
42
|
+
opt_output_format! opt
|
43
|
+
unused_keys = i18n.unused_keys(opt)
|
44
|
+
if unused_keys.present?
|
45
|
+
terminal_report.unused_keys(unused_keys)
|
46
|
+
confirm_remove_unused!(unused_keys, opt)
|
47
|
+
removed = i18n.data.remove_by_key!(unused_keys)
|
48
|
+
log_stderr I18n.t('i18n_tasks.remove_unused.removed', count: unused_keys.leaves.count)
|
49
|
+
print_forest removed, opt
|
50
|
+
else
|
51
|
+
log_stderr bold green I18n.t('i18n_tasks.remove_unused.noop')
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
private
|
56
|
+
|
57
|
+
def confirm_remove_unused!(unused_keys, opt)
|
58
|
+
return if ENV['CONFIRM'] || opt[:confirm]
|
59
|
+
locales = bold(opt[:locales] * ', ')
|
60
|
+
msg = [
|
61
|
+
red(I18n.t('i18n_tasks.remove_unused.confirm', count: unused_keys.leaves.count, locales: locales)),
|
62
|
+
yellow(I18n.t('i18n_tasks.common.continue_q')),
|
63
|
+
yellow('(yes/no)')
|
64
|
+
] * ' '
|
65
|
+
exit 1 unless agree msg
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module I18n::Tasks
|
2
|
+
module Command
|
3
|
+
module Commands
|
4
|
+
module XLSX
|
5
|
+
include Command::Collection
|
6
|
+
|
7
|
+
cmd :xlsx_report,
|
8
|
+
args: '[locale...]',
|
9
|
+
desc: 'save missing and unused translations to an Excel file',
|
10
|
+
opt: [cmd_opt(:locales),
|
11
|
+
{short: :p, long: :path=, desc: 'Destination path', conf: {default: 'tmp/i18n-report.xlsx'}}]
|
12
|
+
|
13
|
+
def xlsx_report(opt = {})
|
14
|
+
begin
|
15
|
+
require 'axlsx'
|
16
|
+
rescue LoadError
|
17
|
+
message = %Q(For spreadsheet report please add axlsx gem to Gemfile:\ngem 'axlsx', '~> 2.0')
|
18
|
+
log_stderr Term::ANSIColor.red Term::ANSIColor.bold message
|
19
|
+
exit 1
|
20
|
+
end
|
21
|
+
opt_locales! opt
|
22
|
+
spreadsheet_report.save_report opt[:path], opt.except(:path)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require 'i18n/tasks/command/dsl/cmd'
|
2
|
+
require 'i18n/tasks/command/dsl/cmd_opt'
|
3
|
+
require 'i18n/tasks/command/dsl/enum_opt'
|
4
|
+
|
5
|
+
module I18n::Tasks
|
6
|
+
module Command
|
7
|
+
module DSL
|
8
|
+
include DSL::Cmd
|
9
|
+
include DSL::CmdOpt
|
10
|
+
include DSL::EnumOpt
|
11
|
+
|
12
|
+
def self.extended(base)
|
13
|
+
base.instance_variable_set :@dsl, HashWithIndifferentAccess.new { |h, k|
|
14
|
+
h[k] = HashWithIndifferentAccess.new
|
15
|
+
}
|
16
|
+
end
|
17
|
+
|
18
|
+
def included(base)
|
19
|
+
base.instance_variable_get(:@dsl).deep_merge!(@dsl)
|
20
|
+
end
|
21
|
+
|
22
|
+
def dsl(key)
|
23
|
+
@dsl[key]
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module I18n::Tasks
|
2
|
+
module Command
|
3
|
+
module DSL
|
4
|
+
module CmdOpt
|
5
|
+
def cmd_opts(*args)
|
6
|
+
dsl(:cmd_opts).values_at(*args)
|
7
|
+
end
|
8
|
+
|
9
|
+
def cmd_opt(arg, opts = nil)
|
10
|
+
if opts
|
11
|
+
dsl(:cmd_opts)[arg] = opts
|
12
|
+
else
|
13
|
+
dsl(:cmd_opts)[arg]
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module I18n::Tasks
|
2
|
+
module Command
|
3
|
+
module DSL
|
4
|
+
module EnumOpt
|
5
|
+
def enum_opt(name, list = nil)
|
6
|
+
if list
|
7
|
+
dsl(:enum_valid)[name] = list
|
8
|
+
else
|
9
|
+
dsl(:enum_valid)[name]
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
DEFAULT_ENUM_OPT_DESC = proc { |valid, default|
|
14
|
+
I18n.t('i18n_tasks.cmd.enum_opt.desc.default', valid_text: valid, default_text: default)
|
15
|
+
}
|
16
|
+
|
17
|
+
def enum_opt_attr(short, long, valid, &desc)
|
18
|
+
desc ||= DEFAULT_ENUM_OPT_DESC
|
19
|
+
{short: short, long: long.to_sym,
|
20
|
+
desc: desc.call(valid * ', ', I18n.t('i18n_tasks.cmd.args.default_text', value: valid.first)),
|
21
|
+
conf: {default: valid.first, argument: true, optional: false}}
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
require 'i18n/tasks/command/options/enum_opt'
|
2
|
+
require 'i18n/tasks/command/options/list_opt'
|
3
|
+
|
4
|
+
module I18n::Tasks
|
5
|
+
module Command
|
6
|
+
module Options
|
7
|
+
module Common
|
8
|
+
extend Command::DSL
|
9
|
+
include Options::EnumOpt
|
10
|
+
include Options::ListOpt
|
11
|
+
|
12
|
+
VALID_LOCALE_RE = /\A\w[\w\-_\.]*\z/i
|
13
|
+
|
14
|
+
cmd_opt :nostdin, {
|
15
|
+
short: :S,
|
16
|
+
long: :nostdin,
|
17
|
+
desc: I18n.t('i18n_tasks.cmd.args.desc.nostdin'),
|
18
|
+
conf: {default: false}
|
19
|
+
}
|
20
|
+
|
21
|
+
cmd_opt :confirm, {
|
22
|
+
short: :y,
|
23
|
+
long: :confirm,
|
24
|
+
desc: I18n.t('i18n_tasks.cmd.args.desc.confirm'),
|
25
|
+
conf: {default: false}
|
26
|
+
}
|
27
|
+
|
28
|
+
cmd_opt :pattern, {
|
29
|
+
short: :p,
|
30
|
+
long: :pattern=,
|
31
|
+
desc: I18n.t('i18n_tasks.cmd.args.desc.key_pattern'),
|
32
|
+
conf: {argument: true, optional: false}
|
33
|
+
}
|
34
|
+
|
35
|
+
cmd_opt :value, {
|
36
|
+
short: :v,
|
37
|
+
long: :value=,
|
38
|
+
desc: I18n.t('i18n_tasks.cmd.args.desc.value'),
|
39
|
+
conf: {argument: true, optional: false}
|
40
|
+
}
|
41
|
+
|
42
|
+
def opt_or_arg!(key, opt)
|
43
|
+
opt[key] ||= opt[:arguments].try(:shift)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
module I18n::Tasks
|
2
|
+
module Command
|
3
|
+
module Options
|
4
|
+
module EnumOpt
|
5
|
+
delegate :enum_opt, to: :class
|
6
|
+
|
7
|
+
DEFAULT_ENUM_OPT_ERROR = proc { |bad, good|
|
8
|
+
I18n.t('i18n_tasks.cmd.enum_opt.invalid_one', invalid: bad, valid: good * ', ')
|
9
|
+
}
|
10
|
+
|
11
|
+
def parse_enum_opt(value, valid, &error_msg)
|
12
|
+
valid = enum_opt(valid) if Symbol === valid
|
13
|
+
return valid.first unless value.present?
|
14
|
+
if enum_opt_valid?(valid, value)
|
15
|
+
value
|
16
|
+
else
|
17
|
+
error_msg ||= DEFAULT_ENUM_OPT_ERROR
|
18
|
+
raise CommandError.new error_msg.call(value, valid)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def enum_opt_valid?(valid, value)
|
23
|
+
valid = enum_opt(valid) if Symbol === valid
|
24
|
+
valid.include?(value)
|
25
|
+
end
|
26
|
+
|
27
|
+
DEFAULT_ENUM_LIST_ERROR = proc { |bad, good|
|
28
|
+
I18n.t('i18n_tasks.cmd.enum_opt.invalid_list', invalid: bad * ', ', valid: good * ', ')
|
29
|
+
}
|
30
|
+
|
31
|
+
def parse_enum_list_opt(values, valid, &error_msg)
|
32
|
+
values = explode_list_opt(values)
|
33
|
+
invalid = values - valid.map(&:to_s)
|
34
|
+
if invalid.empty?
|
35
|
+
values
|
36
|
+
else
|
37
|
+
error_msg ||= DEFAULT_ENUM_LIST_ERROR
|
38
|
+
raise CommandError.new error_msg.call(invalid, valid)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
module I18n::Tasks
|
2
|
+
module Command
|
3
|
+
module Options
|
4
|
+
module ListOpt
|
5
|
+
def explode_list_opt(list_opt, delim = /\s*[,]\s*/)
|
6
|
+
Array(list_opt).compact.map { |v| v.strip.split(delim).compact.presence }.flatten.map(&:presence).compact
|
7
|
+
end
|
8
|
+
end
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
module I18n::Tasks
|
2
|
+
module Command
|
3
|
+
module Options
|
4
|
+
module Locales
|
5
|
+
extend Command::DSL
|
6
|
+
|
7
|
+
cmd_opt :locales, {
|
8
|
+
short: :l,
|
9
|
+
long: :locales=,
|
10
|
+
desc: I18n.t('i18n_tasks.cmd.args.desc.locales_filter'),
|
11
|
+
conf: {as: Array, delimiter: /\s*[+:,]\s*/, default: 'all', argument: true, optional: false}
|
12
|
+
}
|
13
|
+
cmd_opt :locale, {
|
14
|
+
short: :l,
|
15
|
+
long: :locale=,
|
16
|
+
desc: I18n.t('i18n_tasks.cmd.args.desc.locale'),
|
17
|
+
conf: {default: 'base', argument: true, optional: false}
|
18
|
+
}
|
19
|
+
|
20
|
+
def opt_locales!(opt)
|
21
|
+
argv = Array(opt[:arguments]) + Array(opt[:locales])
|
22
|
+
locales = if argv == ['all'] || argv == 'all' || argv.blank?
|
23
|
+
i18n.locales
|
24
|
+
else
|
25
|
+
explode_list_opt(argv).map { |v| v == 'base' ? base_locale : v }
|
26
|
+
end
|
27
|
+
locales.each { |locale| validate_locale!(locale) }
|
28
|
+
log_verbose "locales for the command are #{locales.inspect}"
|
29
|
+
opt[:locales] = locales
|
30
|
+
end
|
31
|
+
|
32
|
+
def opt_locale!(opt, key = :locale)
|
33
|
+
val = opt[key]
|
34
|
+
opt[key] = base_locale if val.blank? || val == 'base'
|
35
|
+
opt[key]
|
36
|
+
end
|
37
|
+
|
38
|
+
|
39
|
+
VALID_LOCALE_RE = /\A\w[\w\-_\.]*\z/i
|
40
|
+
|
41
|
+
def validate_locale!(locale)
|
42
|
+
raise CommandError.new(I18n.t('i18n_tasks.cmd.errors.invalid_locale', invalid: locale)) if VALID_LOCALE_RE !~ locale
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,101 @@
|
|
1
|
+
module I18n::Tasks
|
2
|
+
module Command
|
3
|
+
module Options
|
4
|
+
module Trees
|
5
|
+
extend Command::DSL
|
6
|
+
|
7
|
+
enum_opt :data_format, %w(yaml json keys)
|
8
|
+
cmd_opt :data_format, enum_opt_attr(:f, :format=, enum_opt(:data_format)) { |valid_text, default_text|
|
9
|
+
I18n.t('i18n_tasks.cmd.args.desc.data_format', valid_text: valid_text, default_text: default_text)
|
10
|
+
}
|
11
|
+
|
12
|
+
enum_opt :out_format, ['terminal-table', *enum_opt(:data_format), 'inspect']
|
13
|
+
cmd_opt :out_format, enum_opt_attr(:f, :format=, enum_opt(:out_format)) { |valid_text, default_text|
|
14
|
+
I18n.t('i18n_tasks.cmd.args.desc.out_format', valid_text: valid_text, default_text: default_text)
|
15
|
+
}
|
16
|
+
|
17
|
+
cmd_opt :keys, {
|
18
|
+
short: :k,
|
19
|
+
long: :keys=,
|
20
|
+
desc: I18n.t('i18n_tasks.cmd.args.desc.keys'),
|
21
|
+
conf: {as: Array, delimiter: /[+:,]/, argument: true, optional: false}
|
22
|
+
}
|
23
|
+
|
24
|
+
def print_forest(forest, opt, version = :show_tree)
|
25
|
+
format = opt[:format].to_s
|
26
|
+
|
27
|
+
case format
|
28
|
+
when 'terminal-table'
|
29
|
+
terminal_report.send(version, forest)
|
30
|
+
when 'inspect'
|
31
|
+
puts forest.inspect
|
32
|
+
when 'keys'
|
33
|
+
puts forest.key_names(root: true)
|
34
|
+
when *enum_opt(:data_format)
|
35
|
+
puts i18n.data.adapter_dump forest, format
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
INVALID_FORMAT_MSG = proc do |value, valid|
|
40
|
+
I18n.t('i18n_tasks.cmd.errors.invalid_format', invalid: value, valid: valid * ', ')
|
41
|
+
end
|
42
|
+
|
43
|
+
def opt_output_format!(opt = {}, key = :format)
|
44
|
+
opt[key] = parse_enum_opt opt[key], :out_format, &INVALID_FORMAT_MSG
|
45
|
+
end
|
46
|
+
|
47
|
+
def opt_data_format!(opt = {}, key = :format)
|
48
|
+
opt[key] = parse_enum_opt opt[key], :data_format, &INVALID_FORMAT_MSG
|
49
|
+
end
|
50
|
+
|
51
|
+
def opt_args_keys!(opt = {})
|
52
|
+
opt[:keys] = explode_list_opt(opt[:keys]) + Array(opt[:arguments])
|
53
|
+
end
|
54
|
+
|
55
|
+
def opt_forest_arg_or_stdin!(opt)
|
56
|
+
src = opt[:arguments].try(:shift) || $stdin.read
|
57
|
+
parse_forest(src, opt)
|
58
|
+
end
|
59
|
+
|
60
|
+
def opt_forests_stdin_args!(opt, num = false)
|
61
|
+
args = opt[:arguments] || []
|
62
|
+
if opt[:nostdin]
|
63
|
+
sources = []
|
64
|
+
else
|
65
|
+
sources = [$stdin.read]
|
66
|
+
num -= 1 if num
|
67
|
+
end
|
68
|
+
if num
|
69
|
+
num.times { sources << args.shift }
|
70
|
+
else
|
71
|
+
sources += args
|
72
|
+
args.clear
|
73
|
+
end
|
74
|
+
sources.map { |src| parse_forest(src, opt) }
|
75
|
+
end
|
76
|
+
|
77
|
+
def opt_forests_merged_stdin_args!(opt)
|
78
|
+
opt_forests_stdin_args!(opt).inject(i18n.empty_forest) { |result, forest|
|
79
|
+
result.merge! forest
|
80
|
+
}
|
81
|
+
end
|
82
|
+
|
83
|
+
def parse_forest(src, opt = {})
|
84
|
+
if !src
|
85
|
+
raise CommandError.new(I18n.t('i18n_tasks.cmd.errors.pass_forest'))
|
86
|
+
end
|
87
|
+
format = opt_data_format!(opt)
|
88
|
+
if format == 'keys'
|
89
|
+
Data::Tree::Siblings.from_key_names parse_keys(src)
|
90
|
+
else
|
91
|
+
Data::Tree::Siblings.from_nested_hash i18n.data.adapter_parse(src, format)
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
def parse_keys(src)
|
96
|
+
explode_list_opt(src, /\s*[,\s\n]\s*/)
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|