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,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: Яндекс не дал результатов.
|
data/i18n-tasks.gemspec
ADDED
@@ -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
|