i18n-youdao-tasks 0.9.37

Sign up to get free protection for your applications and to get access to all the features.
Files changed (95) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE.txt +22 -0
  3. data/README.md +448 -0
  4. data/Rakefile +13 -0
  5. data/bin/i18n-tasks +15 -0
  6. data/bin/i18n-tasks.cmd +2 -0
  7. data/config/locales/en.yml +129 -0
  8. data/config/locales/ru.yml +131 -0
  9. data/i18n-tasks.gemspec +58 -0
  10. data/lib/i18n/tasks/base_task.rb +52 -0
  11. data/lib/i18n/tasks/cli.rb +214 -0
  12. data/lib/i18n/tasks/command/collection.rb +21 -0
  13. data/lib/i18n/tasks/command/commander.rb +38 -0
  14. data/lib/i18n/tasks/command/commands/data.rb +107 -0
  15. data/lib/i18n/tasks/command/commands/eq_base.rb +22 -0
  16. data/lib/i18n/tasks/command/commands/health.rb +30 -0
  17. data/lib/i18n/tasks/command/commands/interpolations.rb +22 -0
  18. data/lib/i18n/tasks/command/commands/meta.rb +37 -0
  19. data/lib/i18n/tasks/command/commands/missing.rb +73 -0
  20. data/lib/i18n/tasks/command/commands/tree.rb +102 -0
  21. data/lib/i18n/tasks/command/commands/usages.rb +81 -0
  22. data/lib/i18n/tasks/command/dsl.rb +56 -0
  23. data/lib/i18n/tasks/command/option_parsers/enum.rb +57 -0
  24. data/lib/i18n/tasks/command/option_parsers/locale.rb +60 -0
  25. data/lib/i18n/tasks/command/options/common.rb +47 -0
  26. data/lib/i18n/tasks/command/options/data.rb +97 -0
  27. data/lib/i18n/tasks/command/options/locales.rb +44 -0
  28. data/lib/i18n/tasks/command_error.rb +15 -0
  29. data/lib/i18n/tasks/commands.rb +29 -0
  30. data/lib/i18n/tasks/concurrent/cache.rb +22 -0
  31. data/lib/i18n/tasks/concurrent/cached_value.rb +61 -0
  32. data/lib/i18n/tasks/configuration.rb +136 -0
  33. data/lib/i18n/tasks/console_context.rb +76 -0
  34. data/lib/i18n/tasks/data/adapter/json_adapter.rb +29 -0
  35. data/lib/i18n/tasks/data/adapter/yaml_adapter.rb +27 -0
  36. data/lib/i18n/tasks/data/file_formats.rb +99 -0
  37. data/lib/i18n/tasks/data/file_system.rb +14 -0
  38. data/lib/i18n/tasks/data/file_system_base.rb +200 -0
  39. data/lib/i18n/tasks/data/router/conservative_router.rb +62 -0
  40. data/lib/i18n/tasks/data/router/pattern_router.rb +62 -0
  41. data/lib/i18n/tasks/data/tree/node.rb +206 -0
  42. data/lib/i18n/tasks/data/tree/nodes.rb +97 -0
  43. data/lib/i18n/tasks/data/tree/siblings.rb +333 -0
  44. data/lib/i18n/tasks/data/tree/traversal.rb +197 -0
  45. data/lib/i18n/tasks/data.rb +87 -0
  46. data/lib/i18n/tasks/html_keys.rb +14 -0
  47. data/lib/i18n/tasks/ignore_keys.rb +31 -0
  48. data/lib/i18n/tasks/interpolations.rb +30 -0
  49. data/lib/i18n/tasks/key_pattern_matching.rb +38 -0
  50. data/lib/i18n/tasks/locale_list.rb +19 -0
  51. data/lib/i18n/tasks/locale_pathname.rb +17 -0
  52. data/lib/i18n/tasks/logging.rb +35 -0
  53. data/lib/i18n/tasks/missing_keys.rb +185 -0
  54. data/lib/i18n/tasks/plural_keys.rb +67 -0
  55. data/lib/i18n/tasks/references.rb +103 -0
  56. data/lib/i18n/tasks/reports/base.rb +75 -0
  57. data/lib/i18n/tasks/reports/terminal.rb +243 -0
  58. data/lib/i18n/tasks/scanners/erb_ast_processor.rb +51 -0
  59. data/lib/i18n/tasks/scanners/erb_ast_scanner.rb +48 -0
  60. data/lib/i18n/tasks/scanners/file_scanner.rb +66 -0
  61. data/lib/i18n/tasks/scanners/files/caching_file_finder.rb +35 -0
  62. data/lib/i18n/tasks/scanners/files/caching_file_finder_provider.rb +31 -0
  63. data/lib/i18n/tasks/scanners/files/caching_file_reader.rb +28 -0
  64. data/lib/i18n/tasks/scanners/files/file_finder.rb +61 -0
  65. data/lib/i18n/tasks/scanners/files/file_reader.rb +19 -0
  66. data/lib/i18n/tasks/scanners/local_ruby_parser.rb +74 -0
  67. data/lib/i18n/tasks/scanners/occurrence_from_position.rb +29 -0
  68. data/lib/i18n/tasks/scanners/pattern_mapper.rb +60 -0
  69. data/lib/i18n/tasks/scanners/pattern_scanner.rb +108 -0
  70. data/lib/i18n/tasks/scanners/pattern_with_scope_scanner.rb +100 -0
  71. data/lib/i18n/tasks/scanners/relative_keys.rb +70 -0
  72. data/lib/i18n/tasks/scanners/results/key_occurrences.rb +54 -0
  73. data/lib/i18n/tasks/scanners/results/occurrence.rb +69 -0
  74. data/lib/i18n/tasks/scanners/ruby_ast_call_finder.rb +63 -0
  75. data/lib/i18n/tasks/scanners/ruby_ast_scanner.rb +234 -0
  76. data/lib/i18n/tasks/scanners/ruby_key_literals.rb +30 -0
  77. data/lib/i18n/tasks/scanners/scanner.rb +17 -0
  78. data/lib/i18n/tasks/scanners/scanner_multiplexer.rb +43 -0
  79. data/lib/i18n/tasks/split_key.rb +72 -0
  80. data/lib/i18n/tasks/stats.rb +24 -0
  81. data/lib/i18n/tasks/string_interpolation.rb +17 -0
  82. data/lib/i18n/tasks/translation.rb +29 -0
  83. data/lib/i18n/tasks/translators/base_translator.rb +156 -0
  84. data/lib/i18n/tasks/translators/deepl_translator.rb +81 -0
  85. data/lib/i18n/tasks/translators/google_translator.rb +69 -0
  86. data/lib/i18n/tasks/translators/yandex_translator.rb +63 -0
  87. data/lib/i18n/tasks/translators/youdao_translator.rb +69 -0
  88. data/lib/i18n/tasks/unused_keys.rb +25 -0
  89. data/lib/i18n/tasks/used_keys.rb +184 -0
  90. data/lib/i18n/tasks/version.rb +7 -0
  91. data/lib/i18n/tasks.rb +69 -0
  92. data/templates/config/i18n-tasks.yml +142 -0
  93. data/templates/minitest/i18n_test.rb +36 -0
  94. data/templates/rspec/i18n_spec.rb +34 -0
  95. metadata +441 -0
@@ -0,0 +1,131 @@
1
+ ---
2
+ ru:
3
+ i18n_tasks:
4
+ add_missing:
5
+ added: Добавлены ключи (%{count})
6
+ cmd:
7
+ args:
8
+ default_text: 'По умолчанию: %{value}'
9
+ desc:
10
+ all_locales: Не ожидать, что маски ключа начинаются с локали. Применять маски ко всем локалям.
11
+ config: Путь к файлу конфигурации
12
+ confirm: Подтвердить автоматом
13
+ data_format: 'Формат данных: %{valid_text}.'
14
+ keep_order: Keep the order of the keys
15
+ key_pattern: Маска ключа (например, common.*)
16
+ key_pattern_to_rename: Полный ключ (шаблон) для переименования. Необходимый параметр.
17
+ locale: 'Язык. По умолчанию: base'
18
+ locale_to_translate_from: 'Язык, с которого переводить (по умолчанию: base)'
19
+ locales_filter: >-
20
+ Список языков для обработки, разделенный запятыми (,). По умолчанию: все. Специальное
21
+ значение: base.
22
+ missing_types: 'Типы недостающих переводов: %{valid}. По умолчанию: все'
23
+ new_key_name: Новое имя, интерполирует оригинальное название как %{key}. Необходимый параметр.
24
+ nostdin: Не читать дерево из стандартного ввода
25
+ out_format: 'Формат вывода: %{valid_text}.'
26
+ pattern_router: 'Использовать pattern_router: ключи распределятся по файлам согласно data.write'
27
+ strict: Не угадывать динамические использования ключей, например `t("category.#{category.key}")`
28
+ translation_backend: Движок перевода (google или deepl)
29
+ value: >-
30
+ Значение, интерполируется с %{value}, %{human_key}, %{key}, %{default}, %{value_or_human_key},
31
+ %{value_or_default_or_human_key}
32
+ desc:
33
+ add_missing: добавить недостающие ключи к переводам
34
+ check_consistent_interpolations: убедитесь, что во всех переводах используются правильные
35
+ интерполяционные переменные
36
+ check_normalized: проверить, что все файлы переводов нормализованы
37
+ config: показать конфигурацию
38
+ data: показать данные переводов
39
+ data_merge: добавить дерево к переводам
40
+ data_remove: удалить ключи, которые есть в дереве, из данных
41
+ data_write: заменить переводы деревом
42
+ eq_base: показать переводы, равные значениям в основном языке
43
+ find: показать, где ключи используются в коде
44
+ gem_path: показать путь к ruby gem
45
+ health: Всё ОК?
46
+ irb: начать REPL сессию в контексте i18n-tasks
47
+ missing: показать недостающие переводы
48
+ mv: переименовать / объединить ключи, которые соответствуют заданному шаблону
49
+ normalize: нормализовать файлы переводов (сортировка и распределение)
50
+ remove_unused: удалить неиспользуемые ключи
51
+ rm: удалить ключи, которые соответствуют заданному шаблону
52
+ translate_missing: перевести недостающие переводы с Google Translate / DeepL Pro
53
+ tree_convert: преобразовать дерево между форматами
54
+ tree_filter: фильтровать дерево по ключу
55
+ tree_merge: объединенить деревья
56
+ tree_mv_key: переименованить / объединить / удалить ключи соответствующие заданному шаблону
57
+ tree_set_value: заменить значения ключей
58
+ tree_subtract: дерево A минус ключи в дереве B
59
+ tree_translate: Перевести дерево при помощи Google Translate на язык корневых узлов
60
+ unused: показать неиспользуемые переводы
61
+ encourage:
62
+ - Хорошая работа!
63
+ - Отлично!
64
+ - Прекрасно!
65
+ enum_list_opt:
66
+ invalid: "%{invalid} не в: %{valid}."
67
+ enum_opt:
68
+ invalid: "%{invalid} не является одним из: %{valid}."
69
+ errors:
70
+ invalid_format: 'Неизвестный формат %{invalid}. Форматы: %{valid}.'
71
+ invalid_locale: Неверный язык %{invalid}
72
+ invalid_missing_type:
73
+ few: 'Неизвестные типы: %{invalid}. Типы: %{valid}.'
74
+ many: 'Неизвестные типы: %{invalid}. Типы: %{valid}.'
75
+ one: 'Неизвестный тип %{invalid}. Типы: %{valid}.'
76
+ other: 'Неизвестные типы: %{invalid}. Типы: %{valid}.'
77
+ pass_forest: Передайте дерево
78
+ common:
79
+ continue_q: Продолжить?
80
+ key: Ключ
81
+ locale: Язык
82
+ n_more: ещё %{count}
83
+ value: Значение
84
+ data_stats:
85
+ text: >-
86
+ %{key_count} ключей в %{locale_count} языках. В среднем, длина строки: %{value_chars_avg},
87
+ сегменты ключей: %{key_segments_avg}, ключей в языке %{per_locale_avg}.
88
+ text_single_locale: >-
89
+ %{key_count} ключей. В среднем, длина строки: %{value_chars_avg}, сегменты ключей: %{key_segments_avg}.
90
+ title: 'Данные (%{locales}):'
91
+ deepl_translate:
92
+ errors:
93
+ no_api_key: >-
94
+ Задайте ключ API DeepL через переменную окружения DEEPL_AUTH_KEY или translation.deepl_api_key
95
+ Получите ключ через https://www.deepl.com/pro.
96
+ no_results: DeepL не дал результатов.
97
+ google_translate:
98
+ errors:
99
+ no_api_key: >-
100
+ Задайте ключ API Google через переменную окружения GOOGLE_TRANSLATE_API_KEY или translation.google_translate_api_key
101
+ в config/i18n-tasks.yml. Получите ключ через https://code.google.com/apis/console.
102
+ no_results: >-
103
+ Google Translate не дал результатов. Убедитесь в том, что платежная информация добавлена
104
+ в https://code.google.com/apis/console.
105
+ health:
106
+ no_keys_detected: Ключи не обнаружены. Проверьте data.read в config/i18n-tasks.yml.
107
+ inconsistent_interpolations:
108
+ none: Не найдено несогласованных интерполяций.
109
+ missing:
110
+ details_title: На других языках или в коде
111
+ none: Всё переведено.
112
+ remove_unused:
113
+ confirm:
114
+ few: Переводы (%{count}) будут удалены из %{locales}.
115
+ many: Переводы (%{count}) будут удалены из %{locales}.
116
+ one: "%{count} перевод будут удалён из %{locales}."
117
+ other: Переводы (%{count}) будут удалены из %{locales}.
118
+ noop: Нет неиспользуемых ключей
119
+ removed: Удалены ключи (%{count})
120
+ translate_missing:
121
+ translated: Переведены ключи (%{count})
122
+ unused:
123
+ none: Все переводы используются.
124
+ usages:
125
+ none: Не найдено использований.
126
+ yandex_translate:
127
+ errors:
128
+ no_api_key: |-
129
+ Установить ключ API Яндекса с помощью переменной среды YANDEX_API_KEY или translation.yandex_api_key
130
+ в config / i18n-tasks.yml. Получите ключ по адресу https://tech.yandex.com/translate/.
131
+ no_results: Яндекс не дал результатов.
@@ -0,0 +1,58 @@
1
+ # frozen_string_literal: true
2
+
3
+ lib = File.expand_path('lib', __dir__)
4
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
5
+ require 'i18n/tasks/version'
6
+
7
+ Gem::Specification.new do |s| # rubocop:disable Metrics/BlockLength
8
+ s.name = 'i18n-youdao-tasks'
9
+ s.version = I18n::Tasks::VERSION
10
+ s.authors = ['glebm']
11
+ s.email = ['glex.spb@gmail.com']
12
+ s.license = 'MIT'
13
+ s.summary = 'Manage localization and translation with the awesome power of static analysis'
14
+ s.description = <<~TEXT
15
+ i18n-tasks helps you find and manage missing and unused translations.
16
+
17
+ It analyses code statically for key usages, such as `I18n.t('some.key')`, in order to report keys that are missing or unused,
18
+ pre-fill missing keys (optionally from Google Translate), and remove unused keys.
19
+ TEXT
20
+ s.post_install_message = <<~TEXT
21
+ # Install default configuration:
22
+ cp $(bundle exec i18n-tasks gem-path)/templates/config/i18n-tasks.yml config/
23
+ # Add an RSpec for missing and unused keys:
24
+ cp $(bundle exec i18n-tasks gem-path)/templates/rspec/i18n_spec.rb spec/
25
+ TEXT
26
+ s.homepage = 'https://github.com/glebm/i18n-tasks'
27
+ s.metadata = { 'issue_tracker' => 'https://github.com/glebm/i18n-tasks' } if s.respond_to?(:metadata=)
28
+ s.required_ruby_version = '>= 2.6', '< 4.0' if s.respond_to?(:required_ruby_version=)
29
+
30
+ s.files = `git ls-files`.split($/)
31
+ s.files -= s.files.grep(%r{^(doc/|\.|spec/)}) + %w[CHANGES.md config/i18n-tasks.yml Gemfile]
32
+ 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
+ s.require_paths = ['lib']
35
+
36
+ s.add_dependency 'activesupport', '>= 4.0.2'
37
+ s.add_dependency 'ast', '>= 2.1.0'
38
+ s.add_dependency 'better_html', '~> 1.0'
39
+ s.add_dependency 'erubi'
40
+ s.add_dependency 'highline', '>= 2.0.0'
41
+ s.add_dependency 'i18n'
42
+ s.add_dependency 'parser', '>= 2.2.3.0'
43
+ s.add_dependency 'rails-i18n'
44
+ s.add_dependency 'rainbow', '>= 2.2.2', '< 4.0'
45
+ s.add_dependency 'terminal-table', '>= 1.5.1'
46
+ s.add_development_dependency 'axlsx', '~> 2.0'
47
+ s.add_development_dependency 'bundler', '~> 2.0', '>= 2.0.1'
48
+ s.add_development_dependency 'rake'
49
+ s.add_development_dependency 'rspec', '~> 3.3'
50
+ s.add_development_dependency 'rubocop', '~> 1.6.1'
51
+ s.add_development_dependency 'simplecov'
52
+ s.add_development_dependency 'yard'
53
+
54
+ # Translation backends
55
+ s.add_development_dependency 'deepl-rb', '>= 2.1.0'
56
+ s.add_development_dependency 'easy_translate', '>= 0.5.1' # Google Translate
57
+ s.add_development_dependency 'yandex-translator', '>= 0.3.3'
58
+ end
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'i18n/tasks/command_error'
4
+ require 'i18n/tasks/split_key'
5
+ require 'i18n/tasks/key_pattern_matching'
6
+ require 'i18n/tasks/logging'
7
+ require 'i18n/tasks/plural_keys'
8
+ require 'i18n/tasks/references'
9
+ require 'i18n/tasks/html_keys'
10
+ require 'i18n/tasks/used_keys'
11
+ require 'i18n/tasks/ignore_keys'
12
+ require 'i18n/tasks/missing_keys'
13
+ require 'i18n/tasks/interpolations'
14
+ require 'i18n/tasks/unused_keys'
15
+ require 'i18n/tasks/translation'
16
+ require 'i18n/tasks/locale_pathname'
17
+ require 'i18n/tasks/locale_list'
18
+ require 'i18n/tasks/string_interpolation'
19
+ require 'i18n/tasks/data'
20
+ require 'i18n/tasks/configuration'
21
+ require 'i18n/tasks/stats'
22
+
23
+ module I18n
24
+ module Tasks
25
+ class BaseTask
26
+ include SplitKey
27
+ include KeyPatternMatching
28
+ include PluralKeys
29
+ include References
30
+ include HtmlKeys
31
+ include UsedKeys
32
+ include IgnoreKeys
33
+ include MissingKeys
34
+ include Interpolations
35
+ include UnusedKeys
36
+ include Translation
37
+ include Logging
38
+ include Configuration
39
+ include Data
40
+ include Stats
41
+
42
+ def initialize(config_file: nil, **config)
43
+ @config_override = config_file
44
+ self.config = config || {}
45
+ end
46
+
47
+ def inspect
48
+ "#{self.class.name}#{config_for_inspect}"
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,214 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'i18n/tasks'
4
+ require 'i18n/tasks/commands'
5
+ require 'optparse'
6
+
7
+ class I18n::Tasks::CLI
8
+ include ::I18n::Tasks::Logging
9
+
10
+ def self.start(argv)
11
+ new.start(argv)
12
+ end
13
+
14
+ def initialize; end
15
+
16
+ def start(argv)
17
+ auto_output_coloring do
18
+ exit 1 if run(argv) == :exit1
19
+ rescue OptionParser::ParseError => e
20
+ error e.message, 64
21
+ rescue I18n::Tasks::CommandError => e
22
+ begin
23
+ error e.message, 78
24
+ ensure
25
+ log_verbose e.backtrace * "\n"
26
+ end
27
+ rescue Errno::EPIPE
28
+ # ignore Errno::EPIPE which is throw when pipe breaks, e.g.:
29
+ # i18n-tasks missing | head
30
+ exit 1
31
+ end
32
+ rescue ExecutionError => e
33
+ exit e.exit_code
34
+ end
35
+
36
+ def run(argv)
37
+ argv.each_with_index do |arg, i|
38
+ if ["--config", "-c"].include?(arg)
39
+ if File.exist?(argv[i+1])
40
+ @config_file = argv[i+1]
41
+ break
42
+ else
43
+ error "Config file doesn't exist: #{argv[i+1]}", 128
44
+ end
45
+ end
46
+ end
47
+
48
+ I18n.with_locale(base_task(config_file: @config_file).internal_locale) do
49
+ name, *options = parse!(argv.dup)
50
+ context.run(name, *options)
51
+ end
52
+ end
53
+
54
+ def context
55
+ @context ||= ::I18n::Tasks::Commands.new(base_task)
56
+ end
57
+
58
+ def commands
59
+ # load base task to initialize plugins
60
+ base_task
61
+ @commands ||= ::I18n::Tasks::Commands.cmds.transform_keys { |k| k.to_s.tr('_', '-') }
62
+ end
63
+
64
+ private
65
+
66
+ def base_task(config_file: nil)
67
+ @base_task ||= I18n::Tasks::BaseTask.new(config_file: config_file)
68
+ end
69
+
70
+ def parse!(argv)
71
+ command = parse_command! argv
72
+ options = optparse! command, argv
73
+ parse_options! options, command, argv
74
+ [command.tr('-', '_'), options.update(arguments: argv)]
75
+ end
76
+
77
+ def optparse!(command, argv)
78
+ if command
79
+ optparse_command!(command, argv)
80
+ else
81
+ optparse_no_command!(argv)
82
+ end
83
+ end
84
+
85
+ def optparse_command!(command, argv)
86
+ cmd_conf = commands[command]
87
+ flags = cmd_conf[:args].dup
88
+ options = {}
89
+ OptionParser.new("Usage: #{program_name} #{command} [options] #{cmd_conf[:pos]}".strip) do |op|
90
+ flags.each do |flag|
91
+ op.on(*optparse_args(flag)) { |v| options[option_name(flag)] = v }
92
+ end
93
+ verbose_option op
94
+ help_option op
95
+ end.parse!(argv)
96
+ options
97
+ end
98
+
99
+ def optparse_no_command!(argv)
100
+ argv << '--help' if argv.empty?
101
+ OptionParser.new("Usage: #{program_name} [command] [options]") do |op|
102
+ op.on('-v', '--version', 'Print the version') do
103
+ puts I18n::Tasks::VERSION
104
+ exit
105
+ end
106
+ help_option op
107
+ commands_summary op
108
+ end.parse!(argv)
109
+ end
110
+
111
+ def allow_help_arg_first!(argv)
112
+ # allow `i18n-tasks --help command` in addition to `i18n-tasks command --help`
113
+ argv[0], argv[1] = argv[1], argv[0] if %w[-h --help].include?(argv[0]) && argv[1] && !argv[1].start_with?('-')
114
+ end
115
+
116
+ def parse_command!(argv)
117
+ allow_help_arg_first! argv
118
+ if argv[0] && !argv[0].start_with?('-')
119
+ if commands.keys.include?(argv[0])
120
+ argv.shift
121
+ else
122
+ error "unknown command: #{argv[0]}", 64
123
+ end
124
+ end
125
+ end
126
+
127
+ def verbose_option(op)
128
+ op.on('--verbose', 'Verbose output') do
129
+ ::I18n::Tasks.verbose = true
130
+ end
131
+ end
132
+
133
+ def help_option(op)
134
+ op.on('-h', '--help', 'Show this message') do
135
+ $stderr.puts op
136
+ exit
137
+ end
138
+ end
139
+
140
+ # @param [OptionParser] op
141
+ def commands_summary(op)
142
+ op.separator ''
143
+ op.separator 'Available commands:'
144
+ op.separator ''
145
+ commands.each do |cmd, cmd_conf|
146
+ op.separator " #{cmd.ljust(op.summary_width + 1, ' ')}#{try_call cmd_conf[:desc]}"
147
+ end
148
+ op.separator ''
149
+ op.separator 'See `i18n-tasks <command> --help` for more information on a specific command.'
150
+ end
151
+
152
+ def optparse_args(flag)
153
+ args = flag.dup
154
+ args.map! { |v| try_call v }
155
+ conf = args.extract_options!
156
+ if conf.key?(:default)
157
+ args[-1] = "#{args[-1]}. #{I18n.t('i18n_tasks.cmd.args.default_text', value: conf[:default])}"
158
+ end
159
+ args
160
+ end
161
+
162
+ def parse_options!(options, command, argv)
163
+ commands[command][:args].each do |flag|
164
+ name = option_name flag
165
+ options[name] = parse_option flag, options[name], argv, context
166
+ end
167
+ end
168
+
169
+ def parse_option(flag, val, argv, context)
170
+ conf = flag.last.is_a?(Hash) ? flag.last : {}
171
+ if conf[:consume_positional]
172
+ val = Array(val) + Array(flag.include?(Array) ? argv.flat_map { |x| x.split(',') } : argv)
173
+ end
174
+ val = conf[:default] if val.nil? && conf.key?(:default)
175
+ val = conf[:parser].call(val, context) if conf.key?(:parser)
176
+ val
177
+ end
178
+
179
+ def option_name(flag)
180
+ flag.detect do |f|
181
+ f.start_with?('--')
182
+ end.sub(/\A--(\[no-\])?/, '').sub(/[^\-\w].*\z/, '').to_sym
183
+ end
184
+
185
+ def try_call(v)
186
+ if v.respond_to? :call
187
+ v.call
188
+ else
189
+ v
190
+ end
191
+ end
192
+
193
+ def error(message, exit_code)
194
+ log_error message
195
+ fail ExecutionError.new(message, exit_code)
196
+ end
197
+
198
+ class ExecutionError < RuntimeError
199
+ attr_reader :exit_code
200
+
201
+ def initialize(message, exit_code)
202
+ super(message)
203
+ @exit_code = exit_code
204
+ end
205
+ end
206
+
207
+ def auto_output_coloring(coloring = ENV['I18N_TASKS_COLOR'] || $stdout.isatty)
208
+ coloring_was = Rainbow.enabled
209
+ Rainbow.enabled = coloring
210
+ yield
211
+ ensure
212
+ Rainbow.enabled = coloring_was
213
+ end
214
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
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'
7
+
8
+ module I18n::Tasks
9
+ module Command
10
+ module Collection
11
+ def self.included(base)
12
+ base.module_eval do
13
+ include Command::DSL
14
+ include Command::Options::Common
15
+ include Command::Options::Locales
16
+ include Command::Options::Data
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'i18n/tasks/cli'
4
+ require 'i18n/tasks/reports/terminal'
5
+
6
+ module I18n::Tasks
7
+ module Command
8
+ class Commander
9
+ include ::I18n::Tasks::Logging
10
+
11
+ attr_reader :i18n
12
+
13
+ # @param [I18n::Tasks::BaseTask] i18n
14
+ def initialize(i18n)
15
+ @i18n = i18n
16
+ end
17
+
18
+ def run(name, opts = {})
19
+ name = name.to_sym
20
+ public_name = name.to_s.tr '_', '-'
21
+ log_verbose "task: #{public_name}(#{opts.map { |k, v| "#{k}: #{v.inspect}" } * ', '})"
22
+ if opts.empty? || method(name).arity.zero?
23
+ send name
24
+ else
25
+ send name, **opts
26
+ end
27
+ end
28
+
29
+ protected
30
+
31
+ def terminal_report
32
+ @terminal_report ||= I18n::Tasks::Reports::Terminal.new(i18n)
33
+ end
34
+
35
+ delegate :base_locale, :locales, :t, to: :i18n
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,107 @@
1
+ # frozen_string_literal: true
2
+
3
+ module I18n::Tasks
4
+ module Command
5
+ module Commands
6
+ module Data
7
+ include Command::Collection
8
+
9
+ arg :pattern_router,
10
+ '-p',
11
+ '--pattern_router',
12
+ t('i18n_tasks.cmd.args.desc.pattern_router')
13
+
14
+ cmd :normalize,
15
+ pos: '[locale ...]',
16
+ desc: t('i18n_tasks.cmd.desc.normalize'),
17
+ args: %i[locales pattern_router]
18
+
19
+ def normalize(opt = {})
20
+ i18n.normalize_store! locales: opt[:locales],
21
+ force_pattern_router: opt[:pattern_router]
22
+ end
23
+
24
+ cmd :check_normalized,
25
+ pos: '[locale ...]',
26
+ desc: t('i18n_tasks.cmd.desc.check_normalized'),
27
+ args: %i[locales]
28
+
29
+ def check_normalized(opt)
30
+ non_normalized = i18n.non_normalized_paths locales: opt[:locales]
31
+ terminal_report.check_normalized_results(non_normalized)
32
+ :exit1 unless non_normalized.empty?
33
+ end
34
+
35
+ cmd :mv,
36
+ pos: 'FROM_KEY_PATTERN TO_KEY_PATTERN',
37
+ desc: t('i18n_tasks.cmd.desc.mv')
38
+ def mv(opt = {})
39
+ fail CommandError, 'requires FROM_KEY_PATTERN and TO_KEY_PATTERN' if opt[:arguments].size < 2
40
+
41
+ from_pattern = opt[:arguments].shift
42
+ to_pattern = opt[:arguments].shift
43
+ forest = i18n.data_forest
44
+ results = forest.mv_key!(compile_key_pattern(from_pattern), to_pattern, root: false)
45
+ i18n.data.write forest
46
+ terminal_report.mv_results results
47
+ end
48
+
49
+ cmd :rm,
50
+ pos: 'KEY_PATTERN [KEY_PATTERN...]',
51
+ desc: t('i18n_tasks.cmd.desc.rm')
52
+ def rm(opt = {})
53
+ fail CommandError, 'requires KEY_PATTERN' if opt[:arguments].empty?
54
+
55
+ forest = i18n.data_forest
56
+ results = opt[:arguments].each_with_object({}) do |key_pattern, h|
57
+ h.merge! forest.mv_key!(compile_key_pattern(key_pattern), '', root: false)
58
+ end
59
+ i18n.data.write forest
60
+ terminal_report.mv_results results
61
+ end
62
+
63
+ cmd :data,
64
+ pos: '[locale ...]',
65
+ desc: t('i18n_tasks.cmd.desc.data'),
66
+ args: %i[locales out_format]
67
+
68
+ def data(opt = {})
69
+ print_forest i18n.data_forest(opt[:locales]), opt
70
+ end
71
+
72
+ cmd :data_merge,
73
+ pos: '[tree ...]',
74
+ desc: t('i18n_tasks.cmd.desc.data_merge'),
75
+ args: %i[data_format nostdin]
76
+
77
+ def data_merge(opt = {})
78
+ forest = merge_forests_stdin_and_pos!(opt)
79
+ merged = i18n.data.merge!(forest)
80
+ print_forest merged, opt
81
+ end
82
+
83
+ cmd :data_write,
84
+ pos: '[tree]',
85
+ desc: t('i18n_tasks.cmd.desc.data_write'),
86
+ args: %i[data_format nostdin]
87
+
88
+ def data_write(opt = {})
89
+ forest = forest_pos_or_stdin!(opt)
90
+ i18n.data.write forest
91
+ print_forest forest, opt
92
+ end
93
+
94
+ cmd :data_remove,
95
+ pos: '[tree]',
96
+ desc: t('i18n_tasks.cmd.desc.data_remove'),
97
+ args: %i[data_format nostdin]
98
+
99
+ def data_remove(opt = {})
100
+ removed = i18n.data.remove_by_key!(forest_pos_or_stdin!(opt))
101
+ log_stderr 'Removed:'
102
+ print_forest removed, opt
103
+ end
104
+ end
105
+ end
106
+ end
107
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ module I18n::Tasks
4
+ module Command
5
+ module Commands
6
+ module EqBase
7
+ include Command::Collection
8
+
9
+ cmd :eq_base,
10
+ pos: '[locale ...]',
11
+ desc: t('i18n_tasks.cmd.desc.eq_base'),
12
+ args: %i[locales out_format]
13
+
14
+ def eq_base(opt = {})
15
+ forest = i18n.eq_base_keys(opt)
16
+ print_forest forest, opt, :eq_base_keys
17
+ :exit1 unless forest.empty?
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ module I18n::Tasks
4
+ module Command
5
+ module Commands
6
+ module Health
7
+ include Command::Collection
8
+
9
+ cmd :health,
10
+ pos: '[locale ...]',
11
+ desc: t('i18n_tasks.cmd.desc.health'),
12
+ args: %i[locales out_format config]
13
+
14
+ def health(opt = {})
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?
18
+
19
+ terminal_report.forest_stats forest, stats
20
+ [
21
+ missing(**opt),
22
+ unused(**opt),
23
+ check_consistent_interpolations(**opt),
24
+ check_normalized(**opt)
25
+ ].detect { |result| result == :exit1 }
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end