i18n-processes 0.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 (132) hide show
  1. checksums.yaml +7 -0
  2. data/Gemfile.lock +102 -0
  3. data/LICENSE.txt +21 -0
  4. data/README.md +46 -0
  5. data/Rakefile +12 -0
  6. data/bin/i18n-processes +28 -0
  7. data/bin/i18n-processes.cmd +2 -0
  8. data/config/locales/en.yml +2 -0
  9. data/config/locales/zh-CN.yml +2 -0
  10. data/i18n-processes.gemspec +64 -0
  11. data/lib/i18n/processes/base_process.rb +47 -0
  12. data/lib/i18n/processes/cli.rb +208 -0
  13. data/lib/i18n/processes/command/collection.rb +21 -0
  14. data/lib/i18n/processes/command/commander.rb +43 -0
  15. data/lib/i18n/processes/command/commands/data.rb +107 -0
  16. data/lib/i18n/processes/command/commands/eq_base.rb +21 -0
  17. data/lib/i18n/processes/command/commands/health.rb +26 -0
  18. data/lib/i18n/processes/command/commands/meta.rb +38 -0
  19. data/lib/i18n/processes/command/commands/missing.rb +86 -0
  20. data/lib/i18n/processes/command/commands/preprocessing.rb +90 -0
  21. data/lib/i18n/processes/command/commands/tree.rb +119 -0
  22. data/lib/i18n/processes/command/commands/usages.rb +69 -0
  23. data/lib/i18n/processes/command/commands/xlsx.rb +29 -0
  24. data/lib/i18n/processes/command/dsl.rb +56 -0
  25. data/lib/i18n/processes/command/option_parsers/enum.rb +55 -0
  26. data/lib/i18n/processes/command/option_parsers/locale.rb +60 -0
  27. data/lib/i18n/processes/command/options/common.rb +41 -0
  28. data/lib/i18n/processes/command/options/data.rb +95 -0
  29. data/lib/i18n/processes/command/options/locales.rb +36 -0
  30. data/lib/i18n/processes/command_error.rb +13 -0
  31. data/lib/i18n/processes/commands.rb +31 -0
  32. data/lib/i18n/processes/configuration.rb +132 -0
  33. data/lib/i18n/processes/console_context.rb +76 -0
  34. data/lib/i18n/processes/data/adapter/json_adapter.rb +29 -0
  35. data/lib/i18n/processes/data/adapter/yaml_adapter.rb +27 -0
  36. data/lib/i18n/processes/data/file_formats.rb +111 -0
  37. data/lib/i18n/processes/data/file_system.rb +14 -0
  38. data/lib/i18n/processes/data/file_system_base.rb +205 -0
  39. data/lib/i18n/processes/data/router/conservative_router.rb +66 -0
  40. data/lib/i18n/processes/data/router/pattern_router.rb +60 -0
  41. data/lib/i18n/processes/data/tree/node.rb +204 -0
  42. data/lib/i18n/processes/data/tree/nodes.rb +97 -0
  43. data/lib/i18n/processes/data/tree/siblings.rb +333 -0
  44. data/lib/i18n/processes/data/tree/traversal.rb +190 -0
  45. data/lib/i18n/processes/data.rb +87 -0
  46. data/lib/i18n/processes/google_translation.rb +125 -0
  47. data/lib/i18n/processes/html_keys.rb +16 -0
  48. data/lib/i18n/processes/ignore_keys.rb +30 -0
  49. data/lib/i18n/processes/key_pattern_matching.rb +37 -0
  50. data/lib/i18n/processes/locale_list.rb +19 -0
  51. data/lib/i18n/processes/locale_pathname.rb +17 -0
  52. data/lib/i18n/processes/logging.rb +37 -0
  53. data/lib/i18n/processes/missing_keys.rb +122 -0
  54. data/lib/i18n/processes/path.rb +42 -0
  55. data/lib/i18n/processes/plural_keys.rb +41 -0
  56. data/lib/i18n/processes/rainbow_utils.rb +13 -0
  57. data/lib/i18n/processes/references.rb +101 -0
  58. data/lib/i18n/processes/reports/base.rb +71 -0
  59. data/lib/i18n/processes/reports/spreadsheet.rb +72 -0
  60. data/lib/i18n/processes/reports/terminal.rb +252 -0
  61. data/lib/i18n/processes/scanners/file_scanner.rb +65 -0
  62. data/lib/i18n/processes/scanners/files/caching_file_finder.rb +34 -0
  63. data/lib/i18n/processes/scanners/files/caching_file_finder_provider.rb +33 -0
  64. data/lib/i18n/processes/scanners/files/caching_file_reader.rb +28 -0
  65. data/lib/i18n/processes/scanners/files/file_finder.rb +60 -0
  66. data/lib/i18n/processes/scanners/files/file_reader.rb +19 -0
  67. data/lib/i18n/processes/scanners/occurrence_from_position.rb +27 -0
  68. data/lib/i18n/processes/scanners/pattern_mapper.rb +60 -0
  69. data/lib/i18n/processes/scanners/pattern_scanner.rb +103 -0
  70. data/lib/i18n/processes/scanners/pattern_with_scope_scanner.rb +98 -0
  71. data/lib/i18n/processes/scanners/relative_keys.rb +53 -0
  72. data/lib/i18n/processes/scanners/results/key_occurrences.rb +54 -0
  73. data/lib/i18n/processes/scanners/results/occurrence.rb +69 -0
  74. data/lib/i18n/processes/scanners/ruby_ast_call_finder.rb +62 -0
  75. data/lib/i18n/processes/scanners/ruby_ast_scanner.rb +206 -0
  76. data/lib/i18n/processes/scanners/ruby_key_literals.rb +30 -0
  77. data/lib/i18n/processes/scanners/scanner.rb +17 -0
  78. data/lib/i18n/processes/scanners/scanner_multiplexer.rb +41 -0
  79. data/lib/i18n/processes/split_key.rb +68 -0
  80. data/lib/i18n/processes/stats.rb +24 -0
  81. data/lib/i18n/processes/string_interpolation.rb +16 -0
  82. data/lib/i18n/processes/unused_keys.rb +23 -0
  83. data/lib/i18n/processes/used_keys.rb +177 -0
  84. data/lib/i18n/processes/version.rb +7 -0
  85. data/lib/i18n/processes.rb +69 -0
  86. data/source/p1/_messages/zh/article.properties +9 -0
  87. data/source/p1/_messages/zh/company.properties +62 -0
  88. data/source/p1/_messages/zh/devices.properties +40 -0
  89. data/source/p1/_messages/zh/meeting-rooms.properties +99 -0
  90. data/source/p1/_messages/zh/meetingBooking.properties +18 -0
  91. data/source/p1/_messages/zh/office-areas.properties +64 -0
  92. data/source/p1/_messages/zh/orders.properties +25 -0
  93. data/source/p1/_messages/zh/schedulings.properties +7 -0
  94. data/source/p1/_messages/zh/tag.properties +2 -0
  95. data/source/p1/_messages/zh/ticket.properties +9 -0
  96. data/source/p1/_messages/zh/visitor.properties +5 -0
  97. data/source/p1/messages +586 -0
  98. data/source/p2/orders.properties +25 -0
  99. data/source/p2/schedulings.properties +7 -0
  100. data/source/p2/tag.properties +2 -0
  101. data/source/p2/ticket.properties +9 -0
  102. data/source/p2/visitor.properties +5 -0
  103. data/source/zh.messages.ts +30 -0
  104. data/translated/en/p1/_messages/zh/article.properties +9 -0
  105. data/translated/en/p1/_messages/zh/company.properties +62 -0
  106. data/translated/en/p1/_messages/zh/devices.properties +40 -0
  107. data/translated/en/p1/_messages/zh/meeting-rooms.properties +99 -0
  108. data/translated/en/p1/_messages/zh/meetingBooking.properties +18 -0
  109. data/translated/en/p1/_messages/zh/office-areas.properties +64 -0
  110. data/translated/en/p1/_messages/zh/orders.properties +25 -0
  111. data/translated/en/p1/_messages/zh/schedulings.properties +7 -0
  112. data/translated/en/p1/_messages/zh/tag.properties +2 -0
  113. data/translated/en/p1/_messages/zh/ticket.properties +9 -0
  114. data/translated/en/p1/_messages/zh/visitor.properties +5 -0
  115. data/translated/en/p1/messages +586 -0
  116. data/translated/en/p2/orders.properties +25 -0
  117. data/translated/en/p2/schedulings.properties +7 -0
  118. data/translated/en/p2/tag.properties +2 -0
  119. data/translated/en/p2/ticket.properties +9 -0
  120. data/translated/en/p2/visitor.properties +5 -0
  121. data/translated/en/zh.messages.ts +30 -0
  122. data/translation/en/article.properties +9 -0
  123. data/translation/en/company.properties +56 -0
  124. data/translation/en/meeting-rooms.properties +87 -0
  125. data/translation/en/meetingBooking.properties +14 -0
  126. data/translation/en/messages.en +164 -0
  127. data/translation/en/office-areas.properties +51 -0
  128. data/translation/en/orders.properties +26 -0
  129. data/translation/en/tag.properties +2 -0
  130. data/translation/en/translated +1263 -0
  131. data/translation/en/visitor.properties +4 -0
  132. metadata +408 -0
@@ -0,0 +1,107 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'i18n/processes/command/collection'
4
+ module I18n::Processes
5
+ module Command
6
+ module Commands
7
+ module Data
8
+ include Command::Collection
9
+
10
+ arg :pattern_router,
11
+ '-p',
12
+ '--pattern_router',
13
+ 'Use pattern router: keys moved per config data.write'
14
+
15
+ cmd :normalize,
16
+ pos: '[locale ...]',
17
+ desc: 'normalize translation data: sort and move to the right files',
18
+ args: %i[locales pattern_router]
19
+
20
+ def normalize(opt = {})
21
+ i18n.normalize_store! locales: opt[:locales],
22
+ force_pattern_router: opt[:pattern_router]
23
+ end
24
+
25
+ cmd :check_normalized,
26
+ pos: '[locale ...]',
27
+ desc: 'verify that all translation data is normalized',
28
+ args: %i[locales]
29
+
30
+ def check_normalized(opt)
31
+ non_normalized = i18n.non_normalized_paths locales: opt[:locales]
32
+ terminal_report.check_normalized_results(non_normalized)
33
+ :exit_1 unless non_normalized.empty?
34
+ end
35
+
36
+ cmd :mv,
37
+ pos: 'FROM_KEY_PATTERN TO_KEY_PATTERN',
38
+ desc: 'rename/merge the keys in locale data that match the given pattern'
39
+ def mv(opt = {})
40
+ fail CommandError, 'requires FROM_KEY_PATTERN and TO_KEY_PATTERN' if opt[:arguments].size < 2
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: 'remove the keys in locale data that match the given pattern'
52
+ def rm(opt = {})
53
+ fail CommandError, 'requires KEY_PATTERN' if opt[:arguments].empty?
54
+ forest = i18n.data_forest
55
+ results = opt[:arguments].each_with_object({}) do |key_pattern, h|
56
+ h.merge! forest.mv_key!(compile_key_pattern(key_pattern), '', root: false)
57
+ end
58
+ i18n.data.write forest
59
+ terminal_report.mv_results results
60
+ end
61
+
62
+ cmd :data,
63
+ pos: '[locale ...]',
64
+ desc: 'show locale data',
65
+ args: %i[locales out_format]
66
+
67
+ def data(opt = {})
68
+ $stderr.puts Rainbow("data from here?!")
69
+ print_forest i18n.data_forest(opt[:locales]), opt
70
+ end
71
+
72
+ cmd :data_merge,
73
+ pos: '[tree ...]',
74
+ desc: 'merge locale data with trees',
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: 'replace locale data with tree',
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: 'remove keys present in tree from data',
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,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'i18n/processes/command/collection'
4
+ module I18n::Processes
5
+ module Command
6
+ module Commands
7
+ module EqBase
8
+ include Command::Collection
9
+
10
+ cmd :eq_base,
11
+ pos: '[locale ...]',
12
+ desc: 'show translations equal to base value',
13
+ args: %i[locales out_format]
14
+
15
+ def eq_base(opt = {})
16
+ print_forest i18n.eq_base_keys(opt), opt, :eq_base_keys
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'i18n/processes/command/collection'
4
+ module I18n::Processes
5
+ module Command
6
+ module Commands
7
+ module Health
8
+ include Command::Collection
9
+
10
+ cmd :health,
11
+ pos: '[locale ...]',
12
+ desc: 'is everything OK?',
13
+ args: %i[locales out_format]
14
+
15
+ def health(opt = {})
16
+ forest = i18n.data_forest(opt[:locales])
17
+ stats = i18n.forest_stats(forest)
18
+ fail CommandError, 'No keys detected. Check data.read in config/i18n-processes.yml.' if stats[:key_count].zero?
19
+ terminal_report.forest_stats forest, stats
20
+ $stderr.puts Rainbow("#{opt}").green
21
+ [missing(opt), unused(opt), check_normalized(opt)].detect { |result| result == :exit_1 }
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'i18n/processes/command/collection'
4
+ module I18n::Processes
5
+ module Command
6
+ module Commands
7
+ module Meta
8
+ include Command::Collection
9
+
10
+ cmd :config,
11
+ pos: '[section ...]',
12
+ desc: 'display i18n-processes configuration'
13
+
14
+ def config(opts = {})
15
+ cfg = i18n.config_for_inspect
16
+ cfg = cfg.slice(*opts[:arguments]) if opts[:arguments].present?
17
+ cfg = cfg.to_yaml
18
+ cfg.sub!(/\A---\n/, '')
19
+ cfg.gsub!(/^([^\s-].+?:)/, Rainbow('\1').cyan.bright)
20
+ puts cfg
21
+ end
22
+
23
+ cmd :gem_path, desc: 'show path to the gem'
24
+
25
+ def gem_path
26
+ puts I18n::Processes.gem_path
27
+ end
28
+
29
+ cmd :irb, desc: 'start REPL session within i18n-processes context'
30
+
31
+ def irb
32
+ require 'i18n/processes/console_context'
33
+ ::I18n::Processes::ConsoleContext.start
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,86 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'i18n/processes/command/collection'
4
+ require 'i18n/processes/command/commands/preprocessing'
5
+ module I18n::Processes
6
+ module Command
7
+ module Commands
8
+ module Missing
9
+ include Command::Collection
10
+ include Command::Commands::Preprocessing
11
+
12
+ missing_types = I18n::Processes::MissingKeys.missing_keys_types
13
+ arg :missing_types,
14
+ '-t',
15
+ "--types #{missing_types * ','}",
16
+ Array,
17
+ "Filter by types: #{missing_types * ', '}",
18
+ parser: OptionParsers::Enum::ListParser.new(
19
+ missing_types,
20
+ proc do |invalid, valid|
21
+ "{:one =>'invalid type: #{invalid}. valid: #{valid}.', :other =>'unknown types: #{invalid}. valid: #{valid}.'}"
22
+ end
23
+ )
24
+
25
+ cmd :missing,
26
+ pos: '[locale ...]',
27
+ desc: 'show missing translations',
28
+ args: [:locales, :out_format, :missing_types, ['-p', '--path PATH', 'Destination path', default: 'tmp/missing_keys']]
29
+
30
+ def missing(opt = {})
31
+ translated_locales = opt[:locales].reject{|x| x == base_locale}
32
+ translated_locales.each do |locale|
33
+ $stderr.puts Rainbow("#{base_locale} to #{locale}\n").green
34
+ preprocessing({:locales => [locale] })
35
+ missing_keys = spreadsheet_report.find_missing(locale)
36
+ missing_count = missing_keys.count
37
+ if missing_count.zero?
38
+ spreadsheet_report.translated_files(locale)
39
+ else
40
+ $stderr.puts Rainbow("#{missing_count} keys need to be translated to #{locale}").green
41
+ spreadsheet_report.missing_report(locale)
42
+ end
43
+ end
44
+
45
+ end
46
+
47
+ cmd :translate_missing,
48
+ pos: '[locale ...]',
49
+ desc: 'translate missing keys with Google Translate',
50
+ args: [:locales, :locale_to_translate_from, arg(:out_format).from(1)]
51
+
52
+ def translate_missing(opt = {})
53
+ missing = i18n.missing_diff_forest opt[:locales], opt[:from]
54
+ translated = i18n.google_translate_forest missing, opt[:from]
55
+ i18n.data.merge! translated
56
+ log_stderr "Translated #{translated.leaves.count} keys"
57
+ print_forest translated, opt
58
+ end
59
+
60
+ # cmd :add_missing,
61
+ # pos: '[locale ...]',
62
+ # desc: 'add missing keys to locale data',
63
+ # args: [:locales, :out_format, arg(:value) + [{ default: '%{value_or_default_or_human_key}' }],
64
+ # ['--nil-value', 'Set value to nil. Takes precedence over the value argument.']]
65
+
66
+ # def add_missing(opt = {}) # rubocop:disable Metrics/AbcSize
67
+ # [ # Merge base locale first, as this may affect the value for the other locales
68
+ # [i18n.base_locale] & opt[:locales],
69
+ # opt[:locales] - [i18n.base_locale]
70
+ # ].reject(&:empty?).each_with_object(i18n.empty_forest) do |locales, added|
71
+ # forest = i18n.missing_keys(locales: locales, **opt.slice(:types, :base_locale))
72
+ # .set_each_value!(opt[:'nil-value'] ? nil : opt[:value])
73
+ #
74
+ # # call method I18n::Processes::Data::FileSystemBase merge! to generate file
75
+ # i18n.data.merge! forest
76
+ # # call method I18n::Processes::Data::Tree::Nodes::Siblings merge! to generate file
77
+ # added.merge! forest
78
+ # end.tap do |added|
79
+ # log_stderr "{:one => 'Added #{added.leaves.count} key', :other => 'Added #{added.leaves.count} keys'}"
80
+ # print_forest added, opt
81
+ # end
82
+ # end
83
+ end
84
+ end
85
+ end
86
+ end
@@ -0,0 +1,90 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'i18n/processes/command/collection'
4
+ require 'i18n/processes/path'
5
+ require 'fileutils'
6
+
7
+ module I18n::Processes
8
+ module Command
9
+ module Commands
10
+ module Preprocessing
11
+ include Command::Collection
12
+ include Path
13
+
14
+ cmd :preprocessing,
15
+ pos: '[locale...]',
16
+ desc: 'preprocess origin data files into primitive files with key=value format',
17
+ args: %i[locales out_format]
18
+
19
+ def preprocessing(opt = {})
20
+ locale = opt[:locales].include?(base_locale) ? 'zh-CN' : opt[:locales].first
21
+ dic = {}
22
+ origin_files(locale).flatten.each do |file|
23
+ # dic.merge!(origin_file_read(file)) { |key, v1, v2| fail "conflict: #{key}: #{v1}, #{v2} in #{file}" unless v1 == v2 }
24
+ dic.merge!(origin_file_read(file))
25
+ end
26
+ ## 对比文件默认存储路径
27
+ path = 'tmp/'
28
+ keys_source(dic, path, locale)
29
+ $stderr.puts Rainbow('preprocess origin data files into primitive files successfully').green if locale == base_locale
30
+ end
31
+
32
+
33
+ def origin_file_read(file)
34
+ {}.tap do |a|
35
+ File.open(file).read.each_line do |line|
36
+ next if line =~ /^#/ || line == "\n" || !line.include?('.')
37
+ if line.include?('\':')
38
+ line.gsub!(/'|,/, '')
39
+ key = line.split(': ').first.delete(' ')
40
+ value = line.split(': ').last
41
+ else
42
+ key = line.split('=').first.delete(' ')
43
+ value = line.split('=').last
44
+ end
45
+ a[key] = value
46
+ end
47
+ end
48
+ end
49
+
50
+ def keys_source(dic, path, locale)
51
+ filename = path + locale
52
+ previous_file = "#{path}pre_#{locale}"
53
+ File.delete(previous_file) if File.exist?(previous_file)
54
+ File.rename(filename, previous_file) if File.exist?(filename)
55
+ local_file = File.new(filename, 'w')
56
+ dic.map do |key, value|
57
+ value.include?("\n") ? local_file.write("#{key}=#{value}") : local_file.write("#{key}=#{value}\n")
58
+ end
59
+ local_file.close
60
+ changed_keys(previous_file, filename) if File.exist?(previous_file) && locale == base_locale
61
+ end
62
+
63
+ def changed_keys(previous, current)
64
+ previous_dic = get_dic(previous)
65
+ current_dic = get_dic(current)
66
+ diff = current_dic.merge(previous_dic){|k, v1, v2| {:current => v1, :previous => v2} unless v1 == v2 }
67
+ diff.select!{ |k, v| v.is_a?(Hash)}
68
+ print_changed_keys(diff) unless diff == {}
69
+ changed_keys_save(diff) unless diff == {}
70
+ end
71
+
72
+ def changed_keys_save(diff)
73
+ file = 'tmp/changed_keys/changed_keys'
74
+ FileUtils::mkdir_p File.dirname(file) unless Dir.exist?File.dirname(file)
75
+ changed_keys = File.new(file, 'w')
76
+ diff.each do |k, v|
77
+ changed_keys.write "key: #{k}\n"
78
+ changed_keys.write "current: #{v[:current]}"
79
+ changed_keys.write "previous: #{v[:previous]}"
80
+ changed_keys.write "\n"
81
+ end
82
+ changed_keys.close
83
+ log_warn("need to update #{diff.count} keys' translation")
84
+ log_warn("changed_keys save to #{file}")
85
+ end
86
+
87
+ end
88
+ end
89
+ end
90
+ end
@@ -0,0 +1,119 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'i18n/processes/command/collection'
4
+ module I18n::Processes
5
+ module Command
6
+ module Commands
7
+ module Tree
8
+ include Command::Collection
9
+ include I18n::Processes::KeyPatternMatching
10
+
11
+ cmd :tree_translate,
12
+ pos: '[tree (or stdin)]',
13
+ desc: 'Google Translate a tree to root locales',
14
+ args: [:locale_to_translate_from, arg(:data_format).from(1)]
15
+
16
+ def tree_translate(opts = {})
17
+ forest = forest_pos_or_stdin!(opts)
18
+ print_forest i18n.google_translate_forest(forest, opts[:from]), opts
19
+ end
20
+
21
+ cmd :tree_merge,
22
+ pos: '[[tree] [tree] ... (or stdin)]',
23
+ desc: 'merge trees',
24
+ args: %i[data_format nostdin]
25
+
26
+ def tree_merge(opts = {})
27
+ print_forest merge_forests_stdin_and_pos!(opts), opts
28
+ end
29
+
30
+ cmd :tree_filter,
31
+ pos: '[pattern] [tree (or stdin)]',
32
+ desc: 'filter tree by key pattern',
33
+ args: %i[data_format pattern]
34
+
35
+ def tree_filter(opts = {})
36
+ pattern = arg_or_pos! :pattern, opts
37
+ forest = forest_pos_or_stdin! opts
38
+ unless pattern.blank?
39
+ pattern_re = i18n.compile_key_pattern(pattern)
40
+ forest = forest.select_keys { |full_key, _node| full_key =~ pattern_re }
41
+ end
42
+ print_forest forest, opts
43
+ end
44
+
45
+ cmd :tree_rename_key,
46
+ pos: 'KEY_PATTERN NAME [tree (or stdin)]',
47
+ desc: 'rename tree node',
48
+ args: [['-k', '--key KEY_PATTERN', 'Full key (pattern) to rename. Required'],
49
+ ['-n', '--name NAME', 'New name, interpolates original name as %{key}. Required'],
50
+ :data_format]
51
+
52
+ def tree_rename_key(opt = {})
53
+ warn_deprecated 'Use tree-mv instead.'
54
+ key = arg_or_pos! :key, opt
55
+ name = arg_or_pos! :name, opt
56
+ forest = forest_pos_or_stdin! opt
57
+ fail CommandError, 'pass full key to rename (-k, --key)' if key.blank?
58
+ fail CommandError, 'pass new name (-n, --name)' if name.blank?
59
+ forest.rename_each_key!(key, name)
60
+ print_forest forest, opt
61
+ end
62
+
63
+ arg :all_locales,
64
+ '-a',
65
+ '--all-locales',
66
+ 'Do not expect key patterns to start with a locale, instead apply them to all locales implicitly.'
67
+
68
+ cmd :tree_mv,
69
+ pos: 'FROM_KEY_PATTERN TO_KEY_PATTERN [tree (or stdin)]',
70
+ desc: 'rename/merge/remove the keys matching the given pattern',
71
+ args: %i[data_format all_locales]
72
+ def tree_mv(opt = {})
73
+ fail CommandError, 'requires FROM_KEY_PATTERN and TO_KEY_PATTERN' if opt[:arguments].size < 2
74
+ from_pattern = opt[:arguments].shift
75
+ to_pattern = opt[:arguments].shift
76
+ forest = forest_pos_or_stdin!(opt)
77
+ forest.mv_key!(compile_key_pattern(from_pattern), to_pattern, root: !opt[:'all-locales'])
78
+ print_forest forest, opt
79
+ end
80
+
81
+ cmd :tree_subtract,
82
+ pos: '[[tree] [tree] ... (or stdin)]',
83
+ desc: 'tree A minus the keys in tree B',
84
+ args: %i[data_format nostdin]
85
+
86
+ def tree_subtract(opt = {})
87
+ forests = forests_stdin_and_pos! opt, 2
88
+ forest = forests.reduce(:subtract_by_key) || empty_forest
89
+ print_forest forest, opt
90
+ end
91
+
92
+ cmd :tree_set_value,
93
+ pos: '[VALUE] [tree (or stdin)]',
94
+ desc: 'set values of keys, optionally match a pattern',
95
+ args: %i[value data_format nostdin pattern]
96
+
97
+ def tree_set_value(opt = {})
98
+ value = arg_or_pos! :value, opt
99
+ forest = forest_pos_or_stdin!(opt)
100
+ key_pattern = opt[:pattern]
101
+ fail CommandError, 'pass value (-v, --value)' if value.blank?
102
+ forest.set_each_value!(value, key_pattern)
103
+ print_forest forest, opt
104
+ end
105
+
106
+ cmd :tree_convert,
107
+ pos: '[tree (or stdin)]',
108
+ desc: 'convert tree between formats',
109
+ args: [arg(:data_format).dup.tap { |a| a[0..1] = ['-f', '--from FORMAT'] },
110
+ arg(:out_format).dup.tap { |a| a[0..1] = ['-t', '--to FORMAT'] }]
111
+
112
+ def tree_convert(opt = {})
113
+ forest = forest_pos_or_stdin! opt.merge(format: opt[:from])
114
+ print_forest forest, opt.merge(format: opt[:to])
115
+ end
116
+ end
117
+ end
118
+ end
119
+ end
@@ -0,0 +1,69 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'i18n/processes/command/collection'
4
+ module I18n::Processes
5
+ module Command
6
+ module Commands
7
+ module Usages
8
+ include Command::Collection
9
+
10
+ arg :strict,
11
+ '--[no-]strict',
12
+ 'Avoid inferring dynamic key usages such as t("cats.#{cat}.name"). Takes precedence over the config setting if set.'
13
+
14
+ cmd :find,
15
+ pos: '[pattern]',
16
+ desc: 'show where keys are used in the code',
17
+ args: %i[out_format pattern strict]
18
+
19
+ def find(opt = {})
20
+ opt[:filter] ||= opt.delete(:pattern) || opt[:arguments].try(:first)
21
+ result = i18n.used_tree(strict: opt[:strict], key_filter: opt[:filter].presence, include_raw_references: true)
22
+ print_forest result, opt, :used_keys
23
+ end
24
+
25
+ cmd :unused,
26
+ pos: '[locale ...]',
27
+ desc: 'show unused translations',
28
+ args: %i[locales out_format strict]
29
+
30
+ def unused(opt = {})
31
+ forest = i18n.unused_keys(opt.slice(:locales, :strict))
32
+ print_forest forest, opt, :unused_keys
33
+ :exit_1 unless forest.empty?
34
+ end
35
+
36
+ cmd :remove_unused,
37
+ pos: '[locale ...]',
38
+ desc: 'remove unused keys',
39
+ args: %i[locales out_format strict confirm]
40
+
41
+ def remove_unused(opt = {})
42
+ unused_keys = i18n.unused_keys(opt.slice(:locales, :strict))
43
+ if unused_keys.present?
44
+ terminal_report.unused_keys(unused_keys)
45
+ confirm_remove_unused!(unused_keys, opt)
46
+ removed = i18n.data.remove_by_key!(unused_keys)
47
+ log_stderr "Removed #{unused_keys.leaves.count} keys"
48
+ print_forest removed, opt
49
+ else
50
+ log_stderr Rainbow("No unused keys to remove").green.bright
51
+ end
52
+ end
53
+
54
+ private
55
+
56
+ def confirm_remove_unused!(unused_keys, opt)
57
+ return if ENV['CONFIRM'] || opt[:confirm]
58
+ locales = Rainbow(unused_keys.flat_map { |root| root.key.split('+') }.sort.uniq * ', ').bright
59
+ msg = [
60
+ Rainbow("{:one =>'#{unused_keys.leaves.count} translation will be removed from #{locales}.', :other =>'#{unused_keys.leaves.count} translation will be removed from #{locales}.'}").red,
61
+ Rainbow("Continue?").yellow,
62
+ Rainbow('(yes/no)').yellow
63
+ ].join(' ')
64
+ exit 1 unless agree msg
65
+ end
66
+ end
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'i18n/processes/command/collection'
4
+ module I18n::Processes
5
+ module Command
6
+ module Commands
7
+ module XLSX
8
+ include Command::Collection
9
+
10
+ cmd :xlsx_report,
11
+ pos: '[locale...]',
12
+ desc: 'save missing and unused translations to an Excel file',
13
+ args: [:locales,
14
+ ['-p', '--path PATH', 'Destination path', default: 'tmp/i18n-report.xlsx']]
15
+
16
+ def xlsx_report(opt = {})
17
+ begin
18
+ require 'axlsx'
19
+ rescue LoadError
20
+ message = %(For spreadsheet report please add axlsx gem to Gemfile:\ngem 'axlsx', '~> 2.0')
21
+ log_stderr Rainbow(message).red.bright
22
+ exit 1
23
+ end
24
+ spreadsheet_report.save_report opt[:path], opt.except(:path)
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ module I18n::Processes
4
+ module Command
5
+ module DSL
6
+ def self.included(base)
7
+ base.module_eval do
8
+ @dsl = Hash.new { |h, k| h[k] = {} }
9
+ extend ClassMethods
10
+ end
11
+ end
12
+
13
+ def t(*args)
14
+ I18n.t(*args)
15
+ end
16
+
17
+ module ClassMethods
18
+ def cmd(name, conf = nil)
19
+ if conf
20
+ conf = conf.dup
21
+ conf[:args] = (conf[:args] || []).map { |arg| arg.is_a?(Symbol) ? arg(arg) : arg }
22
+ dsl(:cmds)[name] = conf
23
+ else
24
+ dsl(:cmds)[name]
25
+ end
26
+ end
27
+
28
+ def arg(ref, *args)
29
+ if args.present?
30
+ dsl(:args)[ref] = args
31
+ else
32
+ dsl(:args)[ref]
33
+ end
34
+ end
35
+
36
+ def cmds
37
+ dsl(:cmds)
38
+ end
39
+
40
+ def dsl(key)
41
+ @dsl[key]
42
+ end
43
+
44
+ # late-bound I18n.t for module bodies
45
+ def t(*args)
46
+ proc { I18n.t(*args) }
47
+ end
48
+
49
+ # if class is a module, merge DSL definitions when it is included
50
+ def included(base)
51
+ base.instance_variable_get(:@dsl).deep_merge!(@dsl)
52
+ end
53
+ end
54
+ end
55
+ end
56
+ end