i18n-tasks 1.0.14 → 1.1.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.
Files changed (101) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +138 -39
  3. data/Rakefile +4 -4
  4. data/bin/i18n-tasks +3 -3
  5. data/config/locales/en.yml +17 -1
  6. data/config/locales/ru.yml +18 -1
  7. data/i18n-tasks.gemspec +28 -38
  8. data/lib/i18n/tasks/base_task.rb +19 -19
  9. data/lib/i18n/tasks/cli.rb +37 -30
  10. data/lib/i18n/tasks/command/collection.rb +4 -4
  11. data/lib/i18n/tasks/command/commander.rb +5 -5
  12. data/lib/i18n/tasks/command/commands/check_prism.rb +126 -0
  13. data/lib/i18n/tasks/command/commands/data.rb +33 -33
  14. data/lib/i18n/tasks/command/commands/eq_base.rb +3 -3
  15. data/lib/i18n/tasks/command/commands/health.rb +6 -5
  16. data/lib/i18n/tasks/command/commands/interpolations.rb +14 -3
  17. data/lib/i18n/tasks/command/commands/meta.rb +6 -6
  18. data/lib/i18n/tasks/command/commands/missing.rb +28 -26
  19. data/lib/i18n/tasks/command/commands/tree.rb +33 -33
  20. data/lib/i18n/tasks/command/commands/usages.rb +24 -24
  21. data/lib/i18n/tasks/command/dsl.rb +1 -1
  22. data/lib/i18n/tasks/command/option_parsers/enum.rb +8 -7
  23. data/lib/i18n/tasks/command/option_parsers/locale.rb +4 -4
  24. data/lib/i18n/tasks/command/options/common.rb +16 -16
  25. data/lib/i18n/tasks/command/options/data.rb +18 -18
  26. data/lib/i18n/tasks/command/options/locales.rb +33 -24
  27. data/lib/i18n/tasks/commands.rb +14 -12
  28. data/lib/i18n/tasks/concurrent/cache.rb +1 -1
  29. data/lib/i18n/tasks/concurrent/cached_value.rb +1 -1
  30. data/lib/i18n/tasks/configuration.rb +26 -20
  31. data/lib/i18n/tasks/console_context.rb +11 -11
  32. data/lib/i18n/tasks/data/adapter/json_adapter.rb +1 -1
  33. data/lib/i18n/tasks/data/adapter/yaml_adapter.rb +5 -5
  34. data/lib/i18n/tasks/data/file_formats.rb +3 -3
  35. data/lib/i18n/tasks/data/file_system.rb +5 -5
  36. data/lib/i18n/tasks/data/file_system_base.rb +26 -26
  37. data/lib/i18n/tasks/data/language_names.rb +202 -0
  38. data/lib/i18n/tasks/data/router/conservative_router.rb +3 -3
  39. data/lib/i18n/tasks/data/router/isolating_router.rb +19 -19
  40. data/lib/i18n/tasks/data/router/pattern_router.rb +5 -5
  41. data/lib/i18n/tasks/data/tree/node.rb +27 -27
  42. data/lib/i18n/tasks/data/tree/nodes.rb +10 -10
  43. data/lib/i18n/tasks/data/tree/siblings.rb +20 -20
  44. data/lib/i18n/tasks/data/tree/traversal.rb +5 -5
  45. data/lib/i18n/tasks/data.rb +4 -4
  46. data/lib/i18n/tasks/html_keys.rb +2 -2
  47. data/lib/i18n/tasks/ignore_keys.rb +9 -9
  48. data/lib/i18n/tasks/interpolations.rb +21 -1
  49. data/lib/i18n/tasks/key_pattern_matching.rb +8 -8
  50. data/lib/i18n/tasks/logging.rb +2 -1
  51. data/lib/i18n/tasks/missing_keys.rb +24 -8
  52. data/lib/i18n/tasks/plural_keys.rb +6 -4
  53. data/lib/i18n/tasks/references.rb +4 -4
  54. data/lib/i18n/tasks/reports/base.rb +18 -14
  55. data/lib/i18n/tasks/reports/terminal.rb +64 -47
  56. data/lib/i18n/tasks/scanners/ast_matchers/base_matcher.rb +3 -3
  57. data/lib/i18n/tasks/scanners/ast_matchers/default_i18n_subject_matcher.rb +3 -3
  58. data/lib/i18n/tasks/scanners/ast_matchers/message_receivers_matcher.rb +10 -10
  59. data/lib/i18n/tasks/scanners/ast_matchers/rails_model_matcher.rb +2 -2
  60. data/lib/i18n/tasks/scanners/erb_ast_scanner.rb +69 -10
  61. data/lib/i18n/tasks/scanners/file_scanner.rb +5 -5
  62. data/lib/i18n/tasks/scanners/files/caching_file_finder.rb +3 -3
  63. data/lib/i18n/tasks/scanners/files/caching_file_finder_provider.rb +3 -3
  64. data/lib/i18n/tasks/scanners/files/caching_file_reader.rb +2 -2
  65. data/lib/i18n/tasks/scanners/files/file_finder.rb +8 -8
  66. data/lib/i18n/tasks/scanners/files/file_reader.rb +1 -1
  67. data/lib/i18n/tasks/scanners/local_ruby_parser.rb +9 -9
  68. data/lib/i18n/tasks/scanners/occurrence_from_position.rb +1 -1
  69. data/lib/i18n/tasks/scanners/pattern_mapper.rb +7 -7
  70. data/lib/i18n/tasks/scanners/pattern_scanner.rb +20 -20
  71. data/lib/i18n/tasks/scanners/pattern_with_scope_scanner.rb +8 -8
  72. data/lib/i18n/tasks/scanners/prism_scanners/arguments_visitor.rb +48 -0
  73. data/lib/i18n/tasks/scanners/prism_scanners/nodes.rb +374 -0
  74. data/lib/i18n/tasks/scanners/prism_scanners/visitor.rb +337 -0
  75. data/lib/i18n/tasks/scanners/relative_keys.rb +8 -8
  76. data/lib/i18n/tasks/scanners/results/key_occurrences.rb +3 -3
  77. data/lib/i18n/tasks/scanners/results/occurrence.rb +14 -10
  78. data/lib/i18n/tasks/scanners/ruby_ast_call_finder.rb +1 -1
  79. data/lib/i18n/tasks/scanners/ruby_key_literals.rb +6 -6
  80. data/lib/i18n/tasks/scanners/ruby_parser_factory.rb +27 -0
  81. data/lib/i18n/tasks/scanners/ruby_scanner.rb +225 -0
  82. data/lib/i18n/tasks/scanners/scanner.rb +2 -2
  83. data/lib/i18n/tasks/scanners/scanner_multiplexer.rb +1 -1
  84. data/lib/i18n/tasks/split_key.rb +4 -4
  85. data/lib/i18n/tasks/stats.rb +3 -3
  86. data/lib/i18n/tasks/translation.rb +8 -5
  87. data/lib/i18n/tasks/translators/base_translator.rb +43 -13
  88. data/lib/i18n/tasks/translators/deepl_translator.rb +22 -14
  89. data/lib/i18n/tasks/translators/google_translator.rb +178 -26
  90. data/lib/i18n/tasks/translators/openai_translator.rb +56 -31
  91. data/lib/i18n/tasks/translators/watsonx_translator.rb +155 -0
  92. data/lib/i18n/tasks/translators/yandex_translator.rb +13 -9
  93. data/lib/i18n/tasks/unused_keys.rb +1 -1
  94. data/lib/i18n/tasks/used_keys.rb +32 -32
  95. data/lib/i18n/tasks/version.rb +1 -1
  96. data/lib/i18n/tasks.rb +17 -16
  97. data/templates/config/i18n-tasks.yml +14 -2
  98. data/templates/minitest/i18n_test.rb +3 -3
  99. data/templates/rspec/i18n_spec.rb +7 -7
  100. metadata +38 -172
  101. data/lib/i18n/tasks/scanners/ruby_ast_scanner.rb +0 -145
@@ -1,8 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'i18n/tasks'
4
- require 'i18n/tasks/commands'
5
- require 'optparse'
3
+ require "i18n/tasks"
4
+ require "i18n/tasks/commands"
5
+ require "optparse"
6
6
 
7
7
  class I18n::Tasks::CLI
8
8
  include ::I18n::Tasks::Logging
@@ -11,9 +11,9 @@ class I18n::Tasks::CLI
11
11
  new.start(argv)
12
12
  end
13
13
 
14
- def initialize; end
15
-
16
14
  def start(argv)
15
+ load_dotenv
16
+
17
17
  auto_output_coloring do
18
18
  exit 1 if run(argv) == :exit1
19
19
  rescue OptionParser::ParseError => e
@@ -35,7 +35,7 @@ class I18n::Tasks::CLI
35
35
 
36
36
  def run(argv)
37
37
  argv.each_with_index do |arg, i|
38
- next unless ['--config', '-c'].include?(arg)
38
+ next unless ["--config", "-c"].include?(arg)
39
39
 
40
40
  _, config_file = argv.slice!(i, 2)
41
41
  if File.exist?(config_file)
@@ -59,7 +59,7 @@ class I18n::Tasks::CLI
59
59
  def commands
60
60
  # load base task to initialize plugins
61
61
  base_task
62
- @commands ||= ::I18n::Tasks::Commands.cmds.transform_keys { |k| k.to_s.tr('_', '-') }
62
+ @commands ||= ::I18n::Tasks::Commands.cmds.transform_keys { |k| k.to_s.tr("_", "-") }
63
63
  end
64
64
 
65
65
  private
@@ -72,7 +72,7 @@ class I18n::Tasks::CLI
72
72
  command = parse_command! argv
73
73
  options = optparse! command, argv
74
74
  parse_options! options, command, argv
75
- [command.tr('-', '_'), options.update(arguments: argv)]
75
+ [command.tr("-", "_"), options.update(arguments: argv)]
76
76
  end
77
77
 
78
78
  def optparse!(command, argv)
@@ -85,8 +85,8 @@ class I18n::Tasks::CLI
85
85
 
86
86
  def optparse_command!(command, argv)
87
87
  cmd_conf = commands[command]
88
- flags = cmd_conf[:args].dup
89
- options = {}
88
+ flags = cmd_conf[:args].dup
89
+ options = {}
90
90
  OptionParser.new("Usage: #{program_name} #{command} [options] #{cmd_conf[:pos]}".strip) do |op|
91
91
  flags.each do |flag|
92
92
  op.on(*optparse_args(flag)) { |v| options[option_name(flag)] = v }
@@ -98,9 +98,9 @@ class I18n::Tasks::CLI
98
98
  end
99
99
 
100
100
  def optparse_no_command!(argv)
101
- argv << '--help' if argv.empty?
101
+ argv << "--help" if argv.empty?
102
102
  OptionParser.new("Usage: #{program_name} [command] [options]") do |op|
103
- op.on('-v', '--version', 'Print the version') do
103
+ op.on("-v", "--version", "Print the version") do
104
104
  puts I18n::Tasks::VERSION
105
105
  exit
106
106
  end
@@ -111,12 +111,12 @@ class I18n::Tasks::CLI
111
111
 
112
112
  def allow_help_arg_first!(argv)
113
113
  # allow `i18n-tasks --help command` in addition to `i18n-tasks command --help`
114
- argv[0], argv[1] = argv[1], argv[0] if %w[-h --help].include?(argv[0]) && argv[1] && !argv[1].start_with?('-')
114
+ argv[0], argv[1] = argv[1], argv[0] if %w[-h --help].include?(argv[0]) && argv[1] && !argv[1].start_with?("-")
115
115
  end
116
116
 
117
117
  def parse_command!(argv)
118
118
  allow_help_arg_first! argv
119
- if argv[0] && !argv[0].start_with?('-')
119
+ if argv[0] && !argv[0].start_with?("-")
120
120
  if commands.keys.include?(argv[0])
121
121
  argv.shift
122
122
  else
@@ -126,28 +126,28 @@ class I18n::Tasks::CLI
126
126
  end
127
127
 
128
128
  def verbose_option(op)
129
- op.on('--verbose', 'Verbose output') do
129
+ op.on("--verbose", "Verbose output") do
130
130
  ::I18n::Tasks.verbose = true
131
131
  end
132
132
  end
133
133
 
134
134
  def help_option(op)
135
- op.on('-h', '--help', 'Show this message') do
136
- $stderr.puts op
135
+ op.on("-h", "--help", "Show this message") do
136
+ warn op
137
137
  exit
138
138
  end
139
139
  end
140
140
 
141
141
  # @param [OptionParser] op
142
142
  def commands_summary(op)
143
- op.separator ''
144
- op.separator 'Available commands:'
145
- op.separator ''
143
+ op.separator ""
144
+ op.separator "Available commands:"
145
+ op.separator ""
146
146
  commands.each do |cmd, cmd_conf|
147
- op.separator " #{cmd.ljust(op.summary_width + 1, ' ')}#{try_call cmd_conf[:desc]}"
147
+ op.separator " #{cmd.ljust(op.summary_width + 1, " ")}#{try_call cmd_conf[:desc]}"
148
148
  end
149
- op.separator ''
150
- op.separator 'See `i18n-tasks <command> --help` for more information on a specific command.'
149
+ op.separator ""
150
+ op.separator "See `i18n-tasks <command> --help` for more information on a specific command."
151
151
  end
152
152
 
153
153
  def optparse_args(flag)
@@ -155,14 +155,14 @@ class I18n::Tasks::CLI
155
155
  args.map! { |v| try_call v }
156
156
  conf = args.extract_options!
157
157
  if conf.key?(:default)
158
- args[-1] = "#{args[-1]}. #{I18n.t('i18n_tasks.cmd.args.default_text', value: conf[:default])}"
158
+ args[-1] = "#{args[-1]}. #{I18n.t("i18n_tasks.cmd.args.default_text", value: conf[:default])}"
159
159
  end
160
160
  args
161
161
  end
162
162
 
163
163
  def parse_options!(options, command, argv)
164
164
  commands[command][:args].each do |flag|
165
- name = option_name flag
165
+ name = option_name flag
166
166
  options[name] = parse_option flag, options[name], argv, context
167
167
  end
168
168
  end
@@ -170,7 +170,7 @@ class I18n::Tasks::CLI
170
170
  def parse_option(flag, val, argv, context)
171
171
  conf = flag.last.is_a?(Hash) ? flag.last : {}
172
172
  if conf[:consume_positional]
173
- val = Array(val) + Array(flag.include?(Array) ? argv.flat_map { |x| x.split(',') } : argv)
173
+ val = Array(val) + Array(flag.include?(Array) ? argv.flat_map { |x| x.split(",") } : argv)
174
174
  end
175
175
  val = conf[:default] if val.nil? && conf.key?(:default)
176
176
  val = conf[:parser].call(val, context) if conf.key?(:parser)
@@ -179,8 +179,8 @@ class I18n::Tasks::CLI
179
179
 
180
180
  def option_name(flag)
181
181
  flag.detect do |f|
182
- f.start_with?('--')
183
- end.sub(/\A--(\[no-\])?/, '').sub(/[^\-\w].*\z/, '').to_sym
182
+ f.start_with?("--")
183
+ end.sub(/\A--(\[no-\])?/, "").sub(/[^\-\w].*\z/, "").to_sym
184
184
  end
185
185
 
186
186
  def try_call(v)
@@ -205,11 +205,18 @@ class I18n::Tasks::CLI
205
205
  end
206
206
  end
207
207
 
208
- def auto_output_coloring(coloring = ENV['I18N_TASKS_COLOR'] || $stdout.isatty)
209
- coloring_was = Rainbow.enabled
208
+ def auto_output_coloring(coloring = ENV["I18N_TASKS_COLOR"] || $stdout.isatty)
209
+ coloring_was = Rainbow.enabled
210
210
  Rainbow.enabled = coloring
211
211
  yield
212
212
  ensure
213
213
  Rainbow.enabled = coloring_was
214
214
  end
215
+
216
+ def load_dotenv
217
+ require "dotenv"
218
+ Dotenv.load
219
+ rescue LoadError
220
+ # dotenv not available, continue without it
221
+ end
215
222
  end
@@ -1,9 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'i18n/tasks/command/dsl'
4
- require 'i18n/tasks/command/options/common'
5
- require 'i18n/tasks/command/options/locales'
6
- require 'i18n/tasks/command/options/data'
3
+ require "i18n/tasks/command/dsl"
4
+ require "i18n/tasks/command/options/common"
5
+ require "i18n/tasks/command/options/locales"
6
+ require "i18n/tasks/command/options/data"
7
7
 
8
8
  module I18n::Tasks
9
9
  module Command
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'i18n/tasks/cli'
4
- require 'i18n/tasks/reports/terminal'
3
+ require "i18n/tasks/cli"
4
+ require "i18n/tasks/reports/terminal"
5
5
 
6
6
  module I18n::Tasks
7
7
  module Command
@@ -16,10 +16,10 @@ 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
+ log_stderr "#{Rainbow("#StandWith").bg(:blue)}#{Rainbow("Ukraine").bg(:yellow)}"
20
20
  name = name.to_sym
21
- public_name = name.to_s.tr '_', '-'
22
- log_verbose "task: #{public_name}(#{opts.map { |k, v| "#{k}: #{v.inspect}" } * ', '})"
21
+ public_name = name.to_s.tr "_", "-"
22
+ log_verbose "task: #{public_name}(#{opts.map { |k, v| "#{k}: #{v.inspect}" } * ", "})"
23
23
  if opts.empty? || method(name).arity.zero?
24
24
  send name
25
25
  else
@@ -0,0 +1,126 @@
1
+ # frozen_string_literal: true
2
+
3
+ module I18n::Tasks
4
+ module Command
5
+ module Commands
6
+ module CheckPrism
7
+ include Command::Collection
8
+
9
+ arg :prism_mode,
10
+ "--prism_mode MODE",
11
+ "Prism parser mode: 'rails' or 'ruby'. Defaults to 'rails' if Rails is available in the project, otherwise 'ruby'."
12
+
13
+ cmd :check_prism,
14
+ desc: t("i18n_tasks.cmd.desc.check_prism"),
15
+ args: %i[prism_mode]
16
+
17
+ # Run both the default (Parser-based) and the Prism-enabled scanning
18
+ # and produce a plain-text report in tmp/i18n_tasks_check_prism.md suitable
19
+ # for pasting into an issue.
20
+ def check_prism(opt = {})
21
+ now = Time.now
22
+ report_path = File.join(Dir.pwd, "tmp", "i18n_tasks_check_prism.md")
23
+ FileUtils.mkdir_p(File.dirname(report_path))
24
+
25
+ # keep original search config and clear caches helper
26
+ orig_search = (i18n.config[:search] || {}).dup
27
+
28
+ results = {}
29
+
30
+ # determine prism mode: use provided opt or default to rails when Rails present
31
+ chosen_prism_mode = (opt[:prism_mode].presence || (rails_available? ? "rails" : "ruby")).to_s
32
+ unless %w[rails ruby].include?(chosen_prism_mode)
33
+ fail CommandError, "--prism-mode must be 'rails' or 'ruby'"
34
+ end
35
+
36
+ {default: nil, prism: chosen_prism_mode}.each do |label, prism_mode|
37
+ i18n.config[:search] = orig_search.merge(prism: prism_mode)
38
+ # clear caches used by UsedKeys
39
+ i18n.instance_variable_set(:@scanner, nil)
40
+ i18n.instance_variable_set(:@search_config, nil)
41
+ i18n.instance_variable_set(:@keys_used_in_source_tree, nil)
42
+
43
+ tree = i18n.used_in_source_tree
44
+ keys = tree.nodes.select { |n| n.data[:occurrences].present? }
45
+ results[label] = keys.each_with_object({}) do |node, h|
46
+ full_key = node.full_key(root: false)
47
+ h[full_key] = node.data[:occurrences]
48
+ end
49
+ end
50
+
51
+ # restore original config and caches
52
+ i18n.config[:search] = orig_search
53
+ i18n.instance_variable_set(:@scanner, nil)
54
+ i18n.instance_variable_set(:@search_config, nil)
55
+ i18n.instance_variable_set(:@keys_used_in_source_tree, nil)
56
+
57
+ default_keys = results[:default].keys
58
+ prism_keys = results[:prism].keys
59
+
60
+ only_default = (default_keys - prism_keys).sort
61
+ only_prism = (prism_keys - default_keys).sort
62
+ both = (default_keys & prism_keys).sort
63
+
64
+ File.open(report_path, "w") do |f|
65
+ f.puts "# i18n-tasks check_prism report"
66
+ f.puts "Generated at: #{now.utc}"
67
+ f.puts "Prism mode: #{chosen_prism_mode}"
68
+ f.puts "Gem version: #{I18n::Tasks::VERSION}"
69
+ f.puts ""
70
+ f.puts "Summary"
71
+ f.puts "- total keys (default parser): #{default_keys.size}"
72
+ f.puts "- total keys (prism): #{prism_keys.size}"
73
+ f.puts "- keys in both: #{both.size}"
74
+ f.puts "- keys only in default parser: #{only_default.size}"
75
+ f.puts "- keys only in prism: #{only_prism.size}"
76
+ f.puts ""
77
+
78
+ unless only_default.empty?
79
+ f.puts "## Keys found by the default parser but NOT by Prism (#{only_default.size})"
80
+ only_default.each do |k|
81
+ f.puts "\n### #{k}"
82
+ results[:default][k].first(5).each do |occ|
83
+ src = (occ.raw_key || k).to_s
84
+ line = (occ.line || "").strip
85
+ highlighted = line.gsub(src) { |m| "`#{m}`" }
86
+ f.puts "- #{occ.path}:#{occ.line_num} `#{src}` — #{highlighted}"
87
+ end
88
+ end
89
+ end
90
+
91
+ unless only_prism.empty?
92
+ f.puts "## Keys found by Prism but NOT by the default parser (#{only_prism.size})"
93
+ only_prism.each do |k|
94
+ f.puts "\n### #{k}"
95
+ results[:prism][k].each do |occ|
96
+ src = (occ.raw_key || k).to_s
97
+ line = (occ.line || "").strip
98
+ highlighted = line.gsub(src) { |m| "`#{m}`" }
99
+ f.puts "- #{occ.path}:#{occ.line_num} `#{src}` — #{highlighted}"
100
+ end
101
+ end
102
+ end
103
+
104
+ f.puts "\n## Notes"
105
+ f.puts "- This report compares keys discovered by the project default parser and by Prism (rails mode)."
106
+ end
107
+
108
+ log_stderr "Wrote check_prism report: #{report_path}"
109
+ puts File.read(report_path)
110
+ end
111
+
112
+ private
113
+
114
+ def rails_available?
115
+ return true if defined?(Rails)
116
+ return true if Gem.loaded_specs.key?("rails")
117
+ lock = File.join(Dir.pwd, "Gemfile.lock")
118
+ return false unless File.exist?(lock)
119
+ File.read(lock).lines.any? { |l| l.strip.start_with?("rails ") || l.strip =~ /^rails \(/ }
120
+ rescue
121
+ false
122
+ end
123
+ end
124
+ end
125
+ end
126
+ end
@@ -7,24 +7,24 @@ module I18n::Tasks
7
7
  include Command::Collection
8
8
 
9
9
  arg :pattern_router,
10
- '-p',
11
- '--pattern_router',
12
- t('i18n_tasks.cmd.args.desc.pattern_router')
10
+ "-p",
11
+ "--pattern_router",
12
+ t("i18n_tasks.cmd.args.desc.pattern_router")
13
13
 
14
14
  cmd :normalize,
15
- pos: '[locale ...]',
16
- desc: t('i18n_tasks.cmd.desc.normalize'),
17
- args: %i[locales pattern_router]
15
+ pos: "[locale ...]",
16
+ desc: t("i18n_tasks.cmd.desc.normalize"),
17
+ args: %i[locales pattern_router]
18
18
 
19
19
  def normalize(opt = {})
20
20
  i18n.normalize_store! locales: opt[:locales],
21
- force_pattern_router: opt[:pattern_router]
21
+ force_pattern_router: opt[:pattern_router]
22
22
  end
23
23
 
24
24
  cmd :check_normalized,
25
- pos: '[locale ...]',
26
- desc: t('i18n_tasks.cmd.desc.check_normalized'),
27
- args: %i[locales]
25
+ pos: "[locale ...]",
26
+ desc: t("i18n_tasks.cmd.desc.check_normalized"),
27
+ args: %i[locales]
28
28
 
29
29
  def check_normalized(opt)
30
30
  non_normalized = i18n.non_normalized_paths locales: opt[:locales]
@@ -33,10 +33,10 @@ module I18n::Tasks
33
33
  end
34
34
 
35
35
  cmd :mv,
36
- pos: 'FROM_KEY_PATTERN TO_KEY_PATTERN',
37
- desc: t('i18n_tasks.cmd.desc.mv')
36
+ pos: "FROM_KEY_PATTERN TO_KEY_PATTERN",
37
+ desc: t("i18n_tasks.cmd.desc.mv")
38
38
  def mv(opt = {})
39
- fail CommandError, 'requires FROM_KEY_PATTERN and TO_KEY_PATTERN' if opt[:arguments].size < 2
39
+ fail CommandError, "requires FROM_KEY_PATTERN and TO_KEY_PATTERN" if opt[:arguments].size < 2
40
40
 
41
41
  from_pattern = opt[:arguments].shift
42
42
  to_pattern = opt[:arguments].shift
@@ -47,10 +47,10 @@ module I18n::Tasks
47
47
  end
48
48
 
49
49
  cmd :cp,
50
- pos: 'FROM_KEY_PATTERN TO_KEY_PATTERN',
51
- desc: t('i18n_tasks.cmd.desc.cp')
50
+ pos: "FROM_KEY_PATTERN TO_KEY_PATTERN",
51
+ desc: t("i18n_tasks.cmd.desc.cp")
52
52
  def cp(opt = {})
53
- fail CommandError, 'requires FROM_KEY_PATTERN and TO_KEY_PATTERN' if opt[:arguments].size < 2
53
+ fail CommandError, "requires FROM_KEY_PATTERN and TO_KEY_PATTERN" if opt[:arguments].size < 2
54
54
 
55
55
  from_pattern = opt[:arguments].shift
56
56
  to_pattern = opt[:arguments].shift
@@ -61,32 +61,32 @@ module I18n::Tasks
61
61
  end
62
62
 
63
63
  cmd :rm,
64
- pos: 'KEY_PATTERN [KEY_PATTERN...]',
65
- desc: t('i18n_tasks.cmd.desc.rm')
64
+ pos: "KEY_PATTERN [KEY_PATTERN...]",
65
+ desc: t("i18n_tasks.cmd.desc.rm")
66
66
  def rm(opt = {})
67
- fail CommandError, 'requires KEY_PATTERN' if opt[:arguments].empty?
67
+ fail CommandError, "requires KEY_PATTERN" if opt[:arguments].empty?
68
68
 
69
69
  forest = i18n.data_forest
70
70
  results = opt[:arguments].each_with_object({}) do |key_pattern, h|
71
- h.merge! forest.mv_key!(compile_key_pattern(key_pattern), '', root: false)
71
+ h.merge! forest.mv_key!(compile_key_pattern(key_pattern), "", root: false)
72
72
  end
73
73
  i18n.data.write forest
74
74
  terminal_report.mv_results results
75
75
  end
76
76
 
77
77
  cmd :data,
78
- pos: '[locale ...]',
79
- desc: t('i18n_tasks.cmd.desc.data'),
80
- args: %i[locales out_format]
78
+ pos: "[locale ...]",
79
+ desc: t("i18n_tasks.cmd.desc.data"),
80
+ args: %i[locales out_format]
81
81
 
82
82
  def data(opt = {})
83
83
  print_forest i18n.data_forest(opt[:locales]), opt
84
84
  end
85
85
 
86
86
  cmd :data_merge,
87
- pos: '[tree ...]',
88
- desc: t('i18n_tasks.cmd.desc.data_merge'),
89
- args: %i[data_format nostdin]
87
+ pos: "[tree ...]",
88
+ desc: t("i18n_tasks.cmd.desc.data_merge"),
89
+ args: %i[data_format nostdin]
90
90
 
91
91
  def data_merge(opt = {})
92
92
  forest = merge_forests_stdin_and_pos!(opt)
@@ -95,9 +95,9 @@ module I18n::Tasks
95
95
  end
96
96
 
97
97
  cmd :data_write,
98
- pos: '[tree]',
99
- desc: t('i18n_tasks.cmd.desc.data_write'),
100
- args: %i[data_format nostdin]
98
+ pos: "[tree]",
99
+ desc: t("i18n_tasks.cmd.desc.data_write"),
100
+ args: %i[data_format nostdin]
101
101
 
102
102
  def data_write(opt = {})
103
103
  forest = forest_pos_or_stdin!(opt)
@@ -106,13 +106,13 @@ module I18n::Tasks
106
106
  end
107
107
 
108
108
  cmd :data_remove,
109
- pos: '[tree]',
110
- desc: t('i18n_tasks.cmd.desc.data_remove'),
111
- args: %i[data_format nostdin]
109
+ pos: "[tree]",
110
+ desc: t("i18n_tasks.cmd.desc.data_remove"),
111
+ args: %i[data_format nostdin]
112
112
 
113
113
  def data_remove(opt = {})
114
114
  removed = i18n.data.remove_by_key!(forest_pos_or_stdin!(opt))
115
- log_stderr 'Removed:'
115
+ log_stderr "Removed:"
116
116
  print_forest removed, opt
117
117
  end
118
118
  end
@@ -7,9 +7,9 @@ module I18n::Tasks
7
7
  include Command::Collection
8
8
 
9
9
  cmd :eq_base,
10
- pos: '[locale ...]',
11
- desc: t('i18n_tasks.cmd.desc.eq_base'),
12
- args: %i[locales out_format]
10
+ pos: "[locale ...]",
11
+ desc: t("i18n_tasks.cmd.desc.eq_base"),
12
+ args: %i[locales out_format]
13
13
 
14
14
  def eq_base(opt = {})
15
15
  forest = i18n.eq_base_keys(opt)
@@ -7,20 +7,21 @@ module I18n::Tasks
7
7
  include Command::Collection
8
8
 
9
9
  cmd :health,
10
- pos: '[locale ...]',
11
- desc: t('i18n_tasks.cmd.desc.health'),
12
- args: %i[locales out_format config]
10
+ pos: "[locale ...]",
11
+ desc: t("i18n_tasks.cmd.desc.health"),
12
+ args: %i[locales out_format config]
13
13
 
14
14
  def health(opt = {})
15
15
  forest = i18n.data_forest(opt[:locales])
16
- stats = i18n.forest_stats(forest)
17
- fail CommandError, t('i18n_tasks.health.no_keys_detected') if stats[:key_count].zero?
16
+ stats = i18n.forest_stats(forest)
17
+ fail CommandError, t("i18n_tasks.health.no_keys_detected") if stats[:key_count].zero?
18
18
 
19
19
  terminal_report.forest_stats forest, stats
20
20
  [
21
21
  missing(**opt),
22
22
  unused(**opt),
23
23
  check_consistent_interpolations(**opt),
24
+ check_reserved_interpolations(**opt),
24
25
  check_normalized(**opt)
25
26
  ].detect { |result| result == :exit1 }
26
27
  end
@@ -7,15 +7,26 @@ module I18n::Tasks
7
7
  include Command::Collection
8
8
 
9
9
  cmd :check_consistent_interpolations,
10
- pos: '[locale ...]',
11
- desc: t('i18n_tasks.cmd.desc.check_consistent_interpolations'),
12
- args: %i[locales out_format]
10
+ pos: "[locale ...]",
11
+ desc: t("i18n_tasks.cmd.desc.check_consistent_interpolations"),
12
+ args: %i[locales out_format]
13
13
 
14
14
  def check_consistent_interpolations(opt = {})
15
15
  forest = i18n.inconsistent_interpolations(**opt.slice(:locales, :base_locale))
16
16
  print_forest forest, opt, :inconsistent_interpolations
17
17
  :exit1 unless forest.empty?
18
18
  end
19
+
20
+ cmd :check_reserved_interpolations,
21
+ pos: "[locale]",
22
+ desc: t("i18n_tasks.cmd.desc.check_reserved_interpolations"),
23
+ args: %i[locales out_format]
24
+
25
+ def check_reserved_interpolations(opt = {})
26
+ forest = i18n.reserved_interpolations(**opt.slice(:locale))
27
+ print_forest forest, opt, :reserved_interpolations
28
+ :exit1 unless forest.empty?
29
+ end
19
30
  end
20
31
  end
21
32
  end
@@ -7,28 +7,28 @@ module I18n::Tasks
7
7
  include Command::Collection
8
8
 
9
9
  cmd :config,
10
- pos: '[section ...]',
11
- desc: t('i18n_tasks.cmd.desc.config')
10
+ pos: "[section ...]",
11
+ desc: t("i18n_tasks.cmd.desc.config")
12
12
 
13
13
  def config(opts = {})
14
14
  cfg = i18n.config_for_inspect
15
15
  cfg = cfg.slice(*opts[:arguments]) if opts[:arguments].present?
16
16
  cfg = cfg.to_yaml
17
- cfg.sub!(/\A---\n/, '')
17
+ cfg.sub!(/\A---\n/, "")
18
18
  cfg.gsub!(/^([^\s-].+?:)/, Rainbow('\1').cyan.bright)
19
19
  puts cfg
20
20
  end
21
21
 
22
- cmd :gem_path, desc: t('i18n_tasks.cmd.desc.gem_path')
22
+ cmd :gem_path, desc: t("i18n_tasks.cmd.desc.gem_path")
23
23
 
24
24
  def gem_path
25
25
  puts I18n::Tasks.gem_path
26
26
  end
27
27
 
28
- cmd :irb, desc: t('i18n_tasks.cmd.desc.irb')
28
+ cmd :irb, desc: t("i18n_tasks.cmd.desc.irb")
29
29
 
30
30
  def irb
31
- require 'i18n/tasks/console_context'
31
+ require "i18n/tasks/console_context"
32
32
  ::I18n::Tasks::ConsoleContext.start
33
33
  end
34
34
  end