i18n-tasks 1.0.15 → 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 (102) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +85 -13
  3. data/Rakefile +4 -4
  4. data/bin/i18n-tasks +3 -3
  5. data/config/locales/en.yml +6 -0
  6. data/config/locales/ru.yml +7 -0
  7. data/i18n-tasks.gemspec +28 -41
  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 +25 -25
  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 +5 -5
  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 +32 -32
  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 +22 -21
  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 +1 -1
  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 +8 -8
  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 +8 -1
  73. data/lib/i18n/tasks/scanners/prism_scanners/nodes.rb +101 -61
  74. data/lib/i18n/tasks/scanners/prism_scanners/visitor.rb +169 -105
  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 +1 -1
  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 +5 -5
  87. data/lib/i18n/tasks/translators/base_translator.rb +40 -14
  88. data/lib/i18n/tasks/translators/deepl_translator.rb +17 -14
  89. data/lib/i18n/tasks/translators/google_translator.rb +169 -25
  90. data/lib/i18n/tasks/translators/openai_translator.rb +34 -23
  91. data/lib/i18n/tasks/translators/watsonx_translator.rb +16 -16
  92. data/lib/i18n/tasks/translators/yandex_translator.rb +8 -8
  93. data/lib/i18n/tasks/unused_keys.rb +1 -1
  94. data/lib/i18n/tasks/used_keys.rb +32 -33
  95. data/lib/i18n/tasks/version.rb +1 -1
  96. data/lib/i18n/tasks.rb +17 -17
  97. data/templates/config/i18n-tasks.yml +12 -0
  98. data/templates/minitest/i18n_test.rb +3 -3
  99. data/templates/rspec/i18n_spec.rb +7 -7
  100. metadata +25 -185
  101. data/lib/i18n/tasks/scanners/prism_scanner.rb +0 -83
  102. data/lib/i18n/tasks/scanners/ruby_ast_scanner.rb +0 -145
@@ -1,83 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative 'file_scanner'
4
- require_relative 'ruby_ast_scanner'
5
-
6
- module I18n::Tasks::Scanners
7
- class PrismScanner < FileScanner
8
- MAGIC_COMMENT_SKIP_PRISM = 'i18n-tasks-skip-prism'
9
-
10
- def initialize(**args)
11
- unless VISITOR
12
- warn(
13
- 'Please make sure `prism` is available to use this feature. Fallback to Ruby AST Scanner.'
14
- )
15
- end
16
- super
17
-
18
- @fallback = RubyAstScanner.new(**args)
19
- end
20
-
21
- protected
22
-
23
- # Extract all occurrences of translate calls from the file at the given path.
24
- #
25
- # @return [Array<[key, Results::KeyOccurrence]>] each occurrence found in the file
26
- def scan_file(path)
27
- return @fallback.send(:scan_file, path) if VISITOR.nil?
28
-
29
- process_results(path, PARSER.parse_file(path))
30
- rescue Exception => e # rubocop:disable Lint/RescueException
31
- raise(
32
- ::I18n::Tasks::CommandError.new(
33
- e,
34
- "Error scanning #{path}: #{e.message}"
35
- )
36
- )
37
- end
38
-
39
- # Need to have method that can be overridden to be able to test it
40
- def process_results(path, parse_results)
41
- parsed = parse_results.value
42
- comments = parse_results.comments
43
-
44
- return @fallback.send(:scan_file, path) if skip_prism_comment?(comments)
45
-
46
- rails = if config[:prism_visitor].blank?
47
- true
48
- else
49
- config[:prism_visitor] != 'ruby'
50
- end
51
-
52
- visitor = VISITOR.new(comments: comments, rails: rails)
53
- parsed.accept(visitor)
54
-
55
- occurrences = []
56
- visitor.process.each do |translation_call|
57
- result = translation_call.occurrences(path)
58
- occurrences << result if result
59
- end
60
-
61
- occurrences
62
- end
63
-
64
- def skip_prism_comment?(comments)
65
- comments.any? do |comment|
66
- content =
67
- comment.respond_to?(:slice) ? comment.slice : comment.location.slice
68
- content.include?(MAGIC_COMMENT_SKIP_PRISM)
69
- end
70
- end
71
-
72
- # This block handles adding a fallback if the `prism` gem is not available.
73
- begin
74
- require 'prism'
75
- require_relative 'prism_scanners/visitor'
76
- PARSER = Prism
77
- VISITOR = I18n::Tasks::Scanners::PrismScanners::Visitor
78
- rescue LoadError
79
- PARSER = nil
80
- VISITOR = nil
81
- end
82
- end
83
- end
@@ -1,145 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'i18n/tasks/scanners/file_scanner'
4
- require 'i18n/tasks/scanners/relative_keys'
5
- require 'i18n/tasks/scanners/ruby_ast_call_finder'
6
- require 'i18n/tasks/scanners/ruby_parser_factory'
7
- require 'i18n/tasks/scanners/ast_matchers/default_i18n_subject_matcher'
8
- require 'i18n/tasks/scanners/ast_matchers/message_receivers_matcher'
9
- require 'i18n/tasks/scanners/ast_matchers/rails_model_matcher'
10
-
11
- module I18n::Tasks::Scanners
12
- # Scan for I18n.translate calls using whitequark/parser
13
- class RubyAstScanner < FileScanner
14
- include RelativeKeys
15
- include AST::Sexp
16
-
17
- MAGIC_COMMENT_PREFIX = /\A.\s*i18n-tasks-use\s+/.freeze
18
-
19
- def initialize(**args)
20
- super(**args)
21
- @parser = RubyParserFactory.create_parser
22
- @magic_comment_parser = RubyParserFactory.create_parser
23
- @matchers = setup_matchers
24
- end
25
-
26
- protected
27
-
28
- # Extract all occurrences of translate calls from the file at the given path.
29
- #
30
- # @return [Array<[key, Results::KeyOccurrence]>] each occurrence found in the file
31
- def scan_file(path)
32
- ast, comments = path_to_ast_and_comments(path)
33
-
34
- ast_to_occurences(ast) + comments_to_occurences(path, ast, comments)
35
- rescue Exception => e # rubocop:disable Lint/RescueException
36
- raise ::I18n::Tasks::CommandError.new(e, "Error scanning #{path}: #{e.message}")
37
- end
38
-
39
- # Parse file on path and returns AST and comments.
40
- #
41
- # @param path Path to file to parse
42
- # @return [{Parser::AST::Node}, [Parser::Source::Comment]]
43
- def path_to_ast_and_comments(path)
44
- @parser.reset
45
- @parser.parse_with_comments(make_buffer(path))
46
- end
47
-
48
- def keys_relative_to_calling_method?(path)
49
- /controllers|mailers/.match(path)
50
- end
51
-
52
- # Create an {Parser::Source::Buffer} with the given contents.
53
- # The contents are assigned a {Parser::Source::Buffer#raw_source}.
54
- #
55
- # @param path [String] Path to assign as the buffer name.
56
- # @param contents [String]
57
- # @return [Parser::Source::Buffer] file contents
58
- def make_buffer(path, contents = read_file(path))
59
- Parser::Source::Buffer.new(path).tap do |buffer|
60
- buffer.raw_source = contents
61
- end
62
- end
63
-
64
- # Convert an array of {Parser::Source::Comment} to occurrences.
65
- #
66
- # @param path Path to file
67
- # @param ast Parser::AST::Node
68
- # @param comments [Parser::Source::Comment]
69
- # @return [nil, [key, Occurrence]] full absolute key name and the occurrence.
70
- def comments_to_occurences(path, ast, comments)
71
- magic_comments = comments.select { |comment| comment.text =~ MAGIC_COMMENT_PREFIX }
72
- comment_to_node = Parser::Source::Comment.associate_locations(ast, magic_comments).tap do |h|
73
- h.transform_values!(&:first)
74
- end.invert
75
-
76
- magic_comments.flat_map do |comment|
77
- @parser.reset
78
- associated_node = comment_to_node[comment]
79
- ast = @parser.parse(make_buffer(path, comment.text.sub(MAGIC_COMMENT_PREFIX, '').split(/\s+(?=t)/).join('; ')))
80
- calls = RubyAstCallFinder.new.collect_calls(ast)
81
- results = []
82
-
83
- # method_name is not available at this stage
84
- calls.each do |(send_node, _method_name)|
85
- @matchers.each do |matcher|
86
- result = matcher.convert_to_key_occurrences(
87
- send_node,
88
- nil,
89
- location: associated_node || comment.location
90
- )
91
- results << result if result
92
- end
93
- end
94
-
95
- results
96
- end
97
- end
98
-
99
- # Convert {Parser::AST::Node} to occurrences.
100
- #
101
- # @param ast {Parser::Source::Comment}
102
- # @return [nil, [key, Occurrence]] full absolute key name and the occurrence.
103
- def ast_to_occurences(ast)
104
- calls = RubyAstCallFinder.new.collect_calls(ast)
105
- results = []
106
- calls.each do |send_node, method_name|
107
- @matchers.each do |matcher|
108
- result = matcher.convert_to_key_occurrences(send_node, method_name)
109
- results << result if result
110
- end
111
- end
112
-
113
- results
114
- end
115
-
116
- def setup_matchers
117
- if config[:receiver_messages]
118
- config[:receiver_messages].map do |receiver, message|
119
- AstMatchers::MessageReceiversMatcher.new(
120
- receivers: [receiver],
121
- message: message,
122
- scanner: self
123
- )
124
- end
125
- else
126
- matchers = %i[t t! translate translate!].map do |message|
127
- AstMatchers::MessageReceiversMatcher.new(
128
- receivers: [
129
- AST::Node.new(:const, [nil, :I18n]),
130
- nil
131
- ],
132
- message: message,
133
- scanner: self
134
- )
135
- end
136
-
137
- Array(config[:ast_matchers]).each do |class_name|
138
- matchers << ActiveSupport::Inflector.constantize(class_name).new(scanner: self)
139
- end
140
-
141
- matchers
142
- end
143
- end
144
- end
145
- end