i18n-tasks 1.0.14 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +138 -39
- data/Rakefile +4 -4
- data/bin/i18n-tasks +3 -3
- data/config/locales/en.yml +17 -1
- data/config/locales/ru.yml +18 -1
- data/i18n-tasks.gemspec +28 -38
- data/lib/i18n/tasks/base_task.rb +19 -19
- data/lib/i18n/tasks/cli.rb +37 -30
- data/lib/i18n/tasks/command/collection.rb +4 -4
- data/lib/i18n/tasks/command/commander.rb +5 -5
- data/lib/i18n/tasks/command/commands/check_prism.rb +126 -0
- data/lib/i18n/tasks/command/commands/data.rb +33 -33
- data/lib/i18n/tasks/command/commands/eq_base.rb +3 -3
- data/lib/i18n/tasks/command/commands/health.rb +6 -5
- data/lib/i18n/tasks/command/commands/interpolations.rb +14 -3
- data/lib/i18n/tasks/command/commands/meta.rb +6 -6
- data/lib/i18n/tasks/command/commands/missing.rb +28 -26
- data/lib/i18n/tasks/command/commands/tree.rb +33 -33
- data/lib/i18n/tasks/command/commands/usages.rb +24 -24
- data/lib/i18n/tasks/command/dsl.rb +1 -1
- data/lib/i18n/tasks/command/option_parsers/enum.rb +8 -7
- data/lib/i18n/tasks/command/option_parsers/locale.rb +4 -4
- data/lib/i18n/tasks/command/options/common.rb +16 -16
- data/lib/i18n/tasks/command/options/data.rb +18 -18
- data/lib/i18n/tasks/command/options/locales.rb +33 -24
- data/lib/i18n/tasks/commands.rb +14 -12
- data/lib/i18n/tasks/concurrent/cache.rb +1 -1
- data/lib/i18n/tasks/concurrent/cached_value.rb +1 -1
- data/lib/i18n/tasks/configuration.rb +26 -20
- data/lib/i18n/tasks/console_context.rb +11 -11
- data/lib/i18n/tasks/data/adapter/json_adapter.rb +1 -1
- data/lib/i18n/tasks/data/adapter/yaml_adapter.rb +5 -5
- data/lib/i18n/tasks/data/file_formats.rb +3 -3
- data/lib/i18n/tasks/data/file_system.rb +5 -5
- data/lib/i18n/tasks/data/file_system_base.rb +26 -26
- data/lib/i18n/tasks/data/language_names.rb +202 -0
- data/lib/i18n/tasks/data/router/conservative_router.rb +3 -3
- data/lib/i18n/tasks/data/router/isolating_router.rb +19 -19
- data/lib/i18n/tasks/data/router/pattern_router.rb +5 -5
- data/lib/i18n/tasks/data/tree/node.rb +27 -27
- data/lib/i18n/tasks/data/tree/nodes.rb +10 -10
- data/lib/i18n/tasks/data/tree/siblings.rb +20 -20
- data/lib/i18n/tasks/data/tree/traversal.rb +5 -5
- data/lib/i18n/tasks/data.rb +4 -4
- data/lib/i18n/tasks/html_keys.rb +2 -2
- data/lib/i18n/tasks/ignore_keys.rb +9 -9
- data/lib/i18n/tasks/interpolations.rb +21 -1
- data/lib/i18n/tasks/key_pattern_matching.rb +8 -8
- data/lib/i18n/tasks/logging.rb +2 -1
- data/lib/i18n/tasks/missing_keys.rb +24 -8
- data/lib/i18n/tasks/plural_keys.rb +6 -4
- data/lib/i18n/tasks/references.rb +4 -4
- data/lib/i18n/tasks/reports/base.rb +18 -14
- data/lib/i18n/tasks/reports/terminal.rb +64 -47
- data/lib/i18n/tasks/scanners/ast_matchers/base_matcher.rb +3 -3
- data/lib/i18n/tasks/scanners/ast_matchers/default_i18n_subject_matcher.rb +3 -3
- data/lib/i18n/tasks/scanners/ast_matchers/message_receivers_matcher.rb +10 -10
- data/lib/i18n/tasks/scanners/ast_matchers/rails_model_matcher.rb +2 -2
- data/lib/i18n/tasks/scanners/erb_ast_scanner.rb +69 -10
- data/lib/i18n/tasks/scanners/file_scanner.rb +5 -5
- data/lib/i18n/tasks/scanners/files/caching_file_finder.rb +3 -3
- data/lib/i18n/tasks/scanners/files/caching_file_finder_provider.rb +3 -3
- data/lib/i18n/tasks/scanners/files/caching_file_reader.rb +2 -2
- data/lib/i18n/tasks/scanners/files/file_finder.rb +8 -8
- data/lib/i18n/tasks/scanners/files/file_reader.rb +1 -1
- data/lib/i18n/tasks/scanners/local_ruby_parser.rb +9 -9
- data/lib/i18n/tasks/scanners/occurrence_from_position.rb +1 -1
- data/lib/i18n/tasks/scanners/pattern_mapper.rb +7 -7
- data/lib/i18n/tasks/scanners/pattern_scanner.rb +20 -20
- data/lib/i18n/tasks/scanners/pattern_with_scope_scanner.rb +8 -8
- data/lib/i18n/tasks/scanners/prism_scanners/arguments_visitor.rb +48 -0
- data/lib/i18n/tasks/scanners/prism_scanners/nodes.rb +374 -0
- data/lib/i18n/tasks/scanners/prism_scanners/visitor.rb +337 -0
- data/lib/i18n/tasks/scanners/relative_keys.rb +8 -8
- data/lib/i18n/tasks/scanners/results/key_occurrences.rb +3 -3
- data/lib/i18n/tasks/scanners/results/occurrence.rb +14 -10
- data/lib/i18n/tasks/scanners/ruby_ast_call_finder.rb +1 -1
- data/lib/i18n/tasks/scanners/ruby_key_literals.rb +6 -6
- data/lib/i18n/tasks/scanners/ruby_parser_factory.rb +27 -0
- data/lib/i18n/tasks/scanners/ruby_scanner.rb +225 -0
- data/lib/i18n/tasks/scanners/scanner.rb +2 -2
- data/lib/i18n/tasks/scanners/scanner_multiplexer.rb +1 -1
- data/lib/i18n/tasks/split_key.rb +4 -4
- data/lib/i18n/tasks/stats.rb +3 -3
- data/lib/i18n/tasks/translation.rb +8 -5
- data/lib/i18n/tasks/translators/base_translator.rb +43 -13
- data/lib/i18n/tasks/translators/deepl_translator.rb +22 -14
- data/lib/i18n/tasks/translators/google_translator.rb +178 -26
- data/lib/i18n/tasks/translators/openai_translator.rb +56 -31
- data/lib/i18n/tasks/translators/watsonx_translator.rb +155 -0
- data/lib/i18n/tasks/translators/yandex_translator.rb +13 -9
- data/lib/i18n/tasks/unused_keys.rb +1 -1
- data/lib/i18n/tasks/used_keys.rb +32 -32
- data/lib/i18n/tasks/version.rb +1 -1
- data/lib/i18n/tasks.rb +17 -16
- data/templates/config/i18n-tasks.yml +14 -2
- data/templates/minitest/i18n_test.rb +3 -3
- data/templates/rspec/i18n_spec.rb +7 -7
- metadata +38 -172
- data/lib/i18n/tasks/scanners/ruby_ast_scanner.rb +0 -145
|
@@ -1,16 +1,21 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require
|
|
4
|
-
require
|
|
3
|
+
require "i18n/tasks/scanners/ruby_scanner"
|
|
4
|
+
require "i18n/tasks/scanners/local_ruby_parser"
|
|
5
|
+
require "i18n/tasks/scanners/occurrence_from_position"
|
|
6
|
+
require "prism"
|
|
5
7
|
|
|
6
8
|
module I18n::Tasks::Scanners
|
|
7
|
-
# Scan for I18n.translate calls in ERB-file
|
|
8
|
-
class ErbAstScanner <
|
|
9
|
-
|
|
9
|
+
# Scan for I18n.translate calls in ERB-file using regexp and Parser/Prism
|
|
10
|
+
class ErbAstScanner < RubyScanner
|
|
11
|
+
include OccurrenceFromPosition
|
|
10
12
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
13
|
+
DEFAULT_REGEXP = /<%(={1,2}|-|\#-?|%)?(.*?)([-=])?%>/m
|
|
14
|
+
|
|
15
|
+
# Parser scanner, method called in RubyScanner
|
|
16
|
+
def ast_parser_parse_file(path)
|
|
17
|
+
@ruby_parser ||= LocalRubyParser.new(ignore_blocks: true)
|
|
18
|
+
super
|
|
14
19
|
end
|
|
15
20
|
|
|
16
21
|
private
|
|
@@ -34,11 +39,11 @@ module I18n::Tasks::Scanners
|
|
|
34
39
|
stop = match.end(0) - 2 - (tailch&.size || 0)
|
|
35
40
|
|
|
36
41
|
case character
|
|
37
|
-
when
|
|
42
|
+
when "=", nil, "-"
|
|
38
43
|
parsed, parsed_comments = handle_code(buffer, code, start, stop)
|
|
39
44
|
comments.concat(parsed_comments)
|
|
40
45
|
children << parsed unless parsed.nil?
|
|
41
|
-
when
|
|
46
|
+
when "#", "#-"
|
|
42
47
|
comments << handle_comment(buffer, start, stop)
|
|
43
48
|
end
|
|
44
49
|
end
|
|
@@ -74,5 +79,59 @@ module I18n::Tasks::Scanners
|
|
|
74
79
|
)
|
|
75
80
|
::Parser::AST::Node.new(:erb, children, location: location)
|
|
76
81
|
end
|
|
82
|
+
|
|
83
|
+
# Prism scanner, method called in RubyScanner
|
|
84
|
+
def prism_parse_file(path)
|
|
85
|
+
occurrences = []
|
|
86
|
+
content = File.read(path)
|
|
87
|
+
|
|
88
|
+
content.scan(DEFAULT_REGEXP) do |indicator, code, _tailch, _rspace|
|
|
89
|
+
match = Regexp.last_match
|
|
90
|
+
character = indicator ? indicator[0] : nil
|
|
91
|
+
start = match.begin(0) + 2 + (character&.size || 0)
|
|
92
|
+
|
|
93
|
+
case character
|
|
94
|
+
when "=", nil, "-"
|
|
95
|
+
occurrences += process_code(path, code, content, start)
|
|
96
|
+
when "#", "#-"
|
|
97
|
+
occurrences += process_comments(path, code, content, start)
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
occurrences
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def process_code(path, code, content, start)
|
|
105
|
+
return [] if code.strip.empty? # skip empty ERB tags
|
|
106
|
+
|
|
107
|
+
process_prism_results(path, Prism.parse(code)).map do |key, occurrence|
|
|
108
|
+
[
|
|
109
|
+
key,
|
|
110
|
+
occurrence_from_position(
|
|
111
|
+
path,
|
|
112
|
+
content,
|
|
113
|
+
start + occurrence.pos,
|
|
114
|
+
raw_key: occurrence.raw_key
|
|
115
|
+
)
|
|
116
|
+
]
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
def process_comments(path, code, content, start)
|
|
121
|
+
return [] if code.strip.empty?
|
|
122
|
+
|
|
123
|
+
parsed = Prism.parse(code.gsub("i18n-tasks-use ", "#i18n-tasks-use "))
|
|
124
|
+
process_prism_results(path, parsed).map do |key, occurrence|
|
|
125
|
+
[
|
|
126
|
+
key,
|
|
127
|
+
occurrence_from_position(
|
|
128
|
+
path,
|
|
129
|
+
content,
|
|
130
|
+
start + (code.index(key) || occurrence.pos),
|
|
131
|
+
raw_key: occurrence.raw_key
|
|
132
|
+
)
|
|
133
|
+
]
|
|
134
|
+
end
|
|
135
|
+
end
|
|
77
136
|
end
|
|
78
137
|
end
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require
|
|
3
|
+
require "i18n/tasks/scanners/scanner"
|
|
4
4
|
|
|
5
5
|
module I18n::Tasks::Scanners
|
|
6
6
|
# A base class for a scanner that analyses files.
|
|
@@ -16,7 +16,7 @@ module I18n::Tasks::Scanners
|
|
|
16
16
|
file_reader: Files::CachingFileReader.new
|
|
17
17
|
)
|
|
18
18
|
super()
|
|
19
|
-
@config
|
|
19
|
+
@config = config
|
|
20
20
|
@file_reader = file_reader
|
|
21
21
|
@file_finder = file_finder_provider.get(**config.slice(:paths, :only, :exclude))
|
|
22
22
|
end
|
|
@@ -36,7 +36,7 @@ module I18n::Tasks::Scanners
|
|
|
36
36
|
#
|
|
37
37
|
# @return [Array<[key, Results::KeyOccurrence]>] each occurrence found in the file
|
|
38
38
|
def scan_file(_path)
|
|
39
|
-
fail
|
|
39
|
+
fail "Unimplemented"
|
|
40
40
|
end
|
|
41
41
|
|
|
42
42
|
# Read a file. Reads of the same path are cached.
|
|
@@ -53,8 +53,8 @@ module I18n::Tasks::Scanners
|
|
|
53
53
|
# @param (see FileFinder#traverse_files)
|
|
54
54
|
# @yieldparam (see FileFinder#traverse_files)
|
|
55
55
|
# @return (see FileFinder#traverse_files)
|
|
56
|
-
def traverse_files(&
|
|
57
|
-
@file_finder.traverse_files(&
|
|
56
|
+
def traverse_files(&)
|
|
57
|
+
@file_finder.traverse_files(&)
|
|
58
58
|
end
|
|
59
59
|
|
|
60
60
|
# @note This method is cached, it will only access the filesystem on the first invocation.
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require
|
|
4
|
-
require
|
|
3
|
+
require "i18n/tasks/concurrent/cached_value"
|
|
4
|
+
require "i18n/tasks/scanners/files/file_finder"
|
|
5
5
|
|
|
6
6
|
module I18n::Tasks::Scanners::Files
|
|
7
7
|
# Finds the files in the specified search paths with support for exclusion / inclusion patterns.
|
|
@@ -23,7 +23,7 @@ module I18n::Tasks::Scanners::Files
|
|
|
23
23
|
# @yieldparam (see FileFinder#traverse_files)
|
|
24
24
|
# @return (see FileFinder#traverse_files)
|
|
25
25
|
|
|
26
|
-
|
|
26
|
+
alias_method :uncached_find_files, :find_files
|
|
27
27
|
private :uncached_find_files
|
|
28
28
|
|
|
29
29
|
# @note This method is cached, it will only access the filesystem on the first invocation.
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require
|
|
4
|
-
require
|
|
3
|
+
require "i18n/tasks/concurrent/cache"
|
|
4
|
+
require "i18n/tasks/scanners/files/caching_file_finder"
|
|
5
5
|
|
|
6
6
|
module I18n::Tasks::Scanners::Files
|
|
7
7
|
# Finds the files and provides their contents.
|
|
@@ -12,7 +12,7 @@ module I18n::Tasks::Scanners::Files
|
|
|
12
12
|
# @param exclude [Array<String>]
|
|
13
13
|
def initialize(exclude: [])
|
|
14
14
|
@cache = ::I18n::Tasks::Concurrent::Cache.new
|
|
15
|
-
@defaults = {
|
|
15
|
+
@defaults = {exclude: exclude}
|
|
16
16
|
end
|
|
17
17
|
|
|
18
18
|
# Initialize a {CachingFileFinder} or get one from cache based on the constructor arguments.
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require
|
|
4
|
-
require
|
|
3
|
+
require "i18n/tasks/concurrent/cache"
|
|
4
|
+
require "i18n/tasks/scanners/files/file_reader"
|
|
5
5
|
|
|
6
6
|
module I18n::Tasks::Scanners::Files
|
|
7
7
|
# Reads the files in 'rb' mode and UTF-8 encoding.
|
|
@@ -13,10 +13,10 @@ module I18n::Tasks::Scanners::Files
|
|
|
13
13
|
# Files not matching any of the inclusion patterns will be excluded.
|
|
14
14
|
# @param exclude [Arry<String>] {File.fnmatch}-compatible patterns of files to exclude.
|
|
15
15
|
# Files matching any of the exclusion patterns will be excluded even if they match an inclusion pattern.
|
|
16
|
-
def initialize(paths: [
|
|
17
|
-
fail
|
|
16
|
+
def initialize(paths: ["."], only: nil, exclude: [])
|
|
17
|
+
fail "paths argument is required" if paths.nil?
|
|
18
18
|
|
|
19
|
-
@paths
|
|
19
|
+
@paths = paths
|
|
20
20
|
@include = only
|
|
21
21
|
@exclude = exclude || []
|
|
22
22
|
end
|
|
@@ -26,8 +26,8 @@ module I18n::Tasks::Scanners::Files
|
|
|
26
26
|
# @yield [path]
|
|
27
27
|
# @yieldparam path [String] the path of the found file.
|
|
28
28
|
# @return [Array<of block results>]
|
|
29
|
-
def traverse_files(&
|
|
30
|
-
find_files.map(&
|
|
29
|
+
def traverse_files(&)
|
|
30
|
+
find_files.map(&)
|
|
31
31
|
end
|
|
32
32
|
|
|
33
33
|
# @return [Array<String>] found files
|
|
@@ -36,10 +36,10 @@ module I18n::Tasks::Scanners::Files
|
|
|
36
36
|
paths = @paths.select { |p| File.exist?(p) }
|
|
37
37
|
log_warn "None of the search.paths exist #{@paths.inspect}" if paths.empty?
|
|
38
38
|
Find.find(*paths) do |path|
|
|
39
|
-
is_dir
|
|
40
|
-
hidden
|
|
39
|
+
is_dir = File.directory?(path)
|
|
40
|
+
hidden = File.basename(path).start_with?(".") && !%w[. ./].include?(path)
|
|
41
41
|
not_incl = @include && !path_fnmatch_any?(path, @include)
|
|
42
|
-
excl
|
|
42
|
+
excl = path_fnmatch_any?(path, @exclude)
|
|
43
43
|
if is_dir || hidden || not_incl || excl
|
|
44
44
|
Find.prune if is_dir && (hidden || excl)
|
|
45
45
|
else
|
|
@@ -12,7 +12,7 @@ module I18n::Tasks::Scanners::Files
|
|
|
12
12
|
# @return [String] file contents
|
|
13
13
|
def read_file(path)
|
|
14
14
|
result = nil
|
|
15
|
-
File.open(path,
|
|
15
|
+
File.open(path, "rb", encoding: "UTF-8") { |f| result = f.read }
|
|
16
16
|
result
|
|
17
17
|
end
|
|
18
18
|
end
|
|
@@ -1,26 +1,26 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require
|
|
3
|
+
require "i18n/tasks/scanners/ruby_parser_factory"
|
|
4
4
|
|
|
5
5
|
module I18n::Tasks::Scanners
|
|
6
6
|
class LocalRubyParser
|
|
7
7
|
# ignore_blocks feature inspired by shopify/better-html
|
|
8
8
|
# https://github.com/Shopify/better-html/blob/087943ffd2a5877fa977d71532010b0c91239519/lib/better_html/test_helper/ruby_node.rb#L24
|
|
9
|
-
BLOCK_EXPR = /\s*((\s+|\))do|\{)(\s*\|[^|]*\|)?\s*\Z
|
|
9
|
+
BLOCK_EXPR = /\s*((\s+|\))do|\{)(\s*\|[^|]*\|)?\s*\Z/
|
|
10
10
|
|
|
11
11
|
def initialize(ignore_blocks: false)
|
|
12
|
-
@parser =
|
|
12
|
+
@parser = RubyParserFactory.create_parser
|
|
13
13
|
@ignore_blocks = ignore_blocks
|
|
14
14
|
end
|
|
15
15
|
|
|
16
16
|
# Parse string and normalize location
|
|
17
17
|
def parse(source, location: nil)
|
|
18
|
-
buffer = ::Parser::Source::Buffer.new(
|
|
18
|
+
buffer = ::Parser::Source::Buffer.new("(string)")
|
|
19
19
|
buffer.source = if @ignore_blocks
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
20
|
+
source.sub(BLOCK_EXPR, "")
|
|
21
|
+
else
|
|
22
|
+
source
|
|
23
|
+
end
|
|
24
24
|
|
|
25
25
|
@parser.reset
|
|
26
26
|
ast, comments = @parser.parse_with_comments(buffer)
|
|
@@ -42,7 +42,7 @@ module I18n::Tasks::Scanners
|
|
|
42
42
|
node.updated(
|
|
43
43
|
nil,
|
|
44
44
|
node.children.map { |child| normalize_location(child, location) },
|
|
45
|
-
{
|
|
45
|
+
{location: updated_location(location, node.location)}
|
|
46
46
|
)
|
|
47
47
|
end
|
|
48
48
|
|
|
@@ -13,7 +13,7 @@ module I18n
|
|
|
13
13
|
# @return [Results::Occurrence]
|
|
14
14
|
def occurrence_from_position(path, contents, position, raw_key: nil)
|
|
15
15
|
line_begin = contents.rindex(/^/, position - 1)
|
|
16
|
-
line_end
|
|
16
|
+
line_end = contents.index(/.(?=\r?\n|$)/, position)
|
|
17
17
|
Results::Occurrence.new(
|
|
18
18
|
path: path,
|
|
19
19
|
pos: position,
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require
|
|
4
|
-
require
|
|
5
|
-
require
|
|
6
|
-
require
|
|
3
|
+
require "i18n/tasks/scanners/file_scanner"
|
|
4
|
+
require "i18n/tasks/scanners/relative_keys"
|
|
5
|
+
require "i18n/tasks/scanners/occurrence_from_position"
|
|
6
|
+
require "i18n/tasks/scanners/ruby_key_literals"
|
|
7
7
|
|
|
8
8
|
module I18n::Tasks::Scanners
|
|
9
9
|
# Maps the provided patterns to keys.
|
|
@@ -34,14 +34,14 @@ module I18n::Tasks::Scanners
|
|
|
34
34
|
@patterns.flat_map do |pattern, key|
|
|
35
35
|
result = []
|
|
36
36
|
text.scan(pattern) do |_|
|
|
37
|
-
match
|
|
38
|
-
matches
|
|
37
|
+
match = Regexp.last_match
|
|
38
|
+
matches = match.names.map(&:to_sym).zip(match.captures).to_h
|
|
39
39
|
if matches.key?(:key)
|
|
40
40
|
matches[:key] = strip_literal(matches[:key])
|
|
41
41
|
next unless valid_key?(matches[:key])
|
|
42
42
|
end
|
|
43
43
|
result << [absolute_key(format(key, matches), path),
|
|
44
|
-
|
|
44
|
+
occurrence_from_position(path, text, match.offset(0).first)]
|
|
45
45
|
end
|
|
46
46
|
result
|
|
47
47
|
end
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require
|
|
4
|
-
require
|
|
5
|
-
require
|
|
6
|
-
require
|
|
3
|
+
require "i18n/tasks/scanners/file_scanner"
|
|
4
|
+
require "i18n/tasks/scanners/relative_keys"
|
|
5
|
+
require "i18n/tasks/scanners/occurrence_from_position"
|
|
6
|
+
require "i18n/tasks/scanners/ruby_key_literals"
|
|
7
7
|
|
|
8
8
|
module I18n::Tasks::Scanners
|
|
9
9
|
# Scan for I18n.t usages using a simple regular expression.
|
|
@@ -12,15 +12,15 @@ module I18n::Tasks::Scanners
|
|
|
12
12
|
include OccurrenceFromPosition
|
|
13
13
|
include RubyKeyLiterals
|
|
14
14
|
|
|
15
|
-
TRANSLATE_CALL_RE = /(?<=^|[^\p{L}'\-.]|[^\p{L}'-]I18n\.|I18n\.)t(?:!|ranslate!?)
|
|
15
|
+
TRANSLATE_CALL_RE = /(?<=^|[^\p{L}_'\-.]|[^\p{L}'-]I18n\.|I18n\.)t(?:!|ranslate!?)?/
|
|
16
16
|
IGNORE_LINES = {
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
17
|
+
"coffee" => /^\s*#(?!\si18n-tasks-use)/,
|
|
18
|
+
"erb" => /^\s*<%\s*#(?!\si18n-tasks-use)/,
|
|
19
|
+
"es6" => %r{^\s*//(?!\si18n-tasks-use)},
|
|
20
|
+
"haml" => /^\s*-\s*#(?!\si18n-tasks-use)/,
|
|
21
|
+
"js" => %r{^\s*//(?!\si18n-tasks-use)},
|
|
22
|
+
"opal" => /^\s*#(?!\si18n-tasks-use)/,
|
|
23
|
+
"slim" => %r{^\s*(?:-#|/)(?!\si18n-tasks-use)}
|
|
24
24
|
}.freeze
|
|
25
25
|
|
|
26
26
|
def initialize(**args)
|
|
@@ -47,13 +47,13 @@ module I18n::Tasks::Scanners
|
|
|
47
47
|
key = match_to_key(match, path, location)
|
|
48
48
|
next unless key
|
|
49
49
|
|
|
50
|
-
key +=
|
|
50
|
+
key += ":" if key.end_with?(".")
|
|
51
51
|
next unless valid_key?(key)
|
|
52
52
|
|
|
53
53
|
keys << [key, location]
|
|
54
54
|
end
|
|
55
55
|
keys
|
|
56
|
-
rescue
|
|
56
|
+
rescue => e
|
|
57
57
|
raise ::I18n::Tasks::CommandError.new(e, "Error scanning #{path}: #{e.message}")
|
|
58
58
|
end
|
|
59
59
|
|
|
@@ -62,7 +62,7 @@ module I18n::Tasks::Scanners
|
|
|
62
62
|
# @return [String] full absolute key name
|
|
63
63
|
def match_to_key(match, path, location)
|
|
64
64
|
absolute_key(strip_literal(match[0]), path,
|
|
65
|
-
|
|
65
|
+
calling_method: -> { closest_method(location) if key_relative_to_method?(path) })
|
|
66
66
|
end
|
|
67
67
|
|
|
68
68
|
def exclude_line?(line, path)
|
|
@@ -70,11 +70,11 @@ module I18n::Tasks::Scanners
|
|
|
70
70
|
re && re =~ line
|
|
71
71
|
end
|
|
72
72
|
|
|
73
|
-
VALID_KEY_RE_DYNAMIC = /^(#{VALID_KEY_CHARS}|[:\#{@}\[\]])
|
|
73
|
+
VALID_KEY_RE_DYNAMIC = /^(#{VALID_KEY_CHARS}|[:\#{@}\[\]])+$/
|
|
74
74
|
|
|
75
75
|
def valid_key?(key)
|
|
76
76
|
if @config[:strict]
|
|
77
|
-
super
|
|
77
|
+
super
|
|
78
78
|
else
|
|
79
79
|
key =~ VALID_KEY_RE_DYNAMIC
|
|
80
80
|
end
|
|
@@ -85,9 +85,9 @@ module I18n::Tasks::Scanners
|
|
|
85
85
|
end
|
|
86
86
|
|
|
87
87
|
def closest_method(occurrence)
|
|
88
|
-
method = File.readlines(occurrence.path, encoding:
|
|
89
|
-
|
|
90
|
-
method && method.strip.sub(/^def\s*/,
|
|
88
|
+
method = File.readlines(occurrence.path, encoding: "UTF-8")
|
|
89
|
+
.first(occurrence.line_num - 1).reverse_each.find { |x| x =~ /\bdef\b/ }
|
|
90
|
+
method && method.strip.sub(/^def\s*/, "").sub(/[(\s;].*$/, "")
|
|
91
91
|
end
|
|
92
92
|
|
|
93
93
|
# This method only exists for backwards compatibility with monkey-patches and plugins
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require
|
|
3
|
+
require "i18n/tasks/scanners/pattern_scanner"
|
|
4
4
|
|
|
5
5
|
module I18n::Tasks::Scanners
|
|
6
6
|
# Scans for I18n.t(key, scope: ...) usages
|
|
@@ -21,13 +21,13 @@ module I18n::Tasks::Scanners
|
|
|
21
21
|
# @param [String] path
|
|
22
22
|
# @return [String] full absolute key name with scope resolved if any
|
|
23
23
|
def match_to_key(match, path, location)
|
|
24
|
-
key
|
|
24
|
+
key = super
|
|
25
25
|
scope = match[1]
|
|
26
26
|
if scope
|
|
27
27
|
scope_parts = extract_literal_or_array_of_literals(scope)
|
|
28
28
|
return nil if scope_parts.nil? || scope_parts.empty?
|
|
29
29
|
|
|
30
|
-
"#{scope_parts.join(
|
|
30
|
+
"#{scope_parts.join(".")}.#{key}"
|
|
31
31
|
else
|
|
32
32
|
key unless match[0] =~ /\A\w/
|
|
33
33
|
end
|
|
@@ -43,7 +43,7 @@ module I18n::Tasks::Scanners
|
|
|
43
43
|
if val =~ /\A[\w@]/
|
|
44
44
|
"\#{#{val}}"
|
|
45
45
|
else
|
|
46
|
-
super
|
|
46
|
+
super
|
|
47
47
|
end
|
|
48
48
|
end
|
|
49
49
|
|
|
@@ -77,18 +77,18 @@ module I18n::Tasks::Scanners
|
|
|
77
77
|
end
|
|
78
78
|
end
|
|
79
79
|
s.each_char.with_index do |c, i|
|
|
80
|
-
if c ==
|
|
80
|
+
if c == "["
|
|
81
81
|
return nil unless braces_stack.empty?
|
|
82
82
|
|
|
83
83
|
braces_stack.push(i)
|
|
84
|
-
elsif c ==
|
|
84
|
+
elsif c == "]"
|
|
85
85
|
break
|
|
86
|
-
elsif c ==
|
|
86
|
+
elsif c == ","
|
|
87
87
|
consume_literal.call
|
|
88
88
|
break if braces_stack.empty?
|
|
89
89
|
elsif c =~ VALID_KEY_CHARS || /['":]/ =~ c
|
|
90
90
|
acc << c
|
|
91
|
-
elsif c !=
|
|
91
|
+
elsif c != " "
|
|
92
92
|
return nil
|
|
93
93
|
end
|
|
94
94
|
end
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "prism/visitor"
|
|
4
|
+
|
|
5
|
+
# This class is used to parse the arguments to e.g. a Prism::CallNode and return the values we need
|
|
6
|
+
# for turning them into translations and occurrences.
|
|
7
|
+
# Used in the PrismScanners::Visitor class.
|
|
8
|
+
module I18n::Tasks::Scanners::PrismScanners
|
|
9
|
+
class ArgumentsVisitor < Prism::Visitor
|
|
10
|
+
def visit_keyword_hash_node(node)
|
|
11
|
+
node.child_nodes.each_with_object({}) do |child, hash|
|
|
12
|
+
next if child.type == :assoc_splat_node
|
|
13
|
+
|
|
14
|
+
hash[visit(child.key)] = visit(child.value)
|
|
15
|
+
hash
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# Cannot handle arguments that are calls
|
|
20
|
+
def visit_call_node(_node)
|
|
21
|
+
nil
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def visit_symbol_node(node)
|
|
25
|
+
node.value
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def visit_string_node(node)
|
|
29
|
+
node.content
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def visit_array_node(node)
|
|
33
|
+
node.child_nodes.map { |child| visit(child) }
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def visit_arguments_node(node)
|
|
37
|
+
node.child_nodes.map { |child| visit(child) }
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def visit_integer_node(node)
|
|
41
|
+
node.value
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def visit_lambda_node(node)
|
|
45
|
+
node
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|