i18n-tasks 1.0.8 → 1.0.11
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 +17 -4
- data/i18n-tasks.gemspec +9 -4
- data/lib/i18n/tasks/cli.rb +5 -4
- data/lib/i18n/tasks/command/options/common.rb +0 -1
- data/lib/i18n/tasks/command/options/data.rb +1 -1
- data/lib/i18n/tasks/concurrent/cached_value.rb +0 -2
- data/lib/i18n/tasks/configuration.rb +11 -6
- data/lib/i18n/tasks/data/adapter/yaml_adapter.rb +2 -2
- data/lib/i18n/tasks/data/file_formats.rb +1 -1
- data/lib/i18n/tasks/data/router/pattern_router.rb +1 -1
- data/lib/i18n/tasks/data/tree/node.rb +1 -1
- data/lib/i18n/tasks/data/tree/nodes.rb +5 -7
- data/lib/i18n/tasks/data/tree/siblings.rb +1 -2
- data/lib/i18n/tasks/html_keys.rb +2 -2
- data/lib/i18n/tasks/references.rb +2 -2
- data/lib/i18n/tasks/reports/base.rb +1 -1
- data/lib/i18n/tasks/reports/terminal.rb +2 -2
- data/lib/i18n/tasks/scanners/ast_matchers/base_matcher.rb +5 -1
- data/lib/i18n/tasks/scanners/ast_matchers/rails_model_matcher.rb +69 -0
- data/lib/i18n/tasks/scanners/pattern_mapper.rb +1 -1
- data/lib/i18n/tasks/scanners/pattern_scanner.rb +1 -1
- data/lib/i18n/tasks/scanners/relative_keys.rb +2 -2
- data/lib/i18n/tasks/scanners/results/occurrence.rb +1 -1
- data/lib/i18n/tasks/scanners/ruby_ast_scanner.rb +8 -3
- data/lib/i18n/tasks/scanners/ruby_key_literals.rb +2 -2
- data/lib/i18n/tasks/split_key.rb +1 -1
- data/lib/i18n/tasks/translators/base_translator.rb +1 -1
- data/lib/i18n/tasks/translators/deepl_translator.rb +2 -2
- data/lib/i18n/tasks/used_keys.rb +3 -2
- data/lib/i18n/tasks/version.rb +1 -1
- data/lib/i18n/tasks.rb +11 -0
- data/templates/config/i18n-tasks.yml +12 -1
- metadata +18 -14
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 76a7092606f1ea327f57a9a2bc0489c07d5349f61b6b742415d0dd367b243237
|
4
|
+
data.tar.gz: dd87c3098d774df0fe685829cefe3716e9cd42156cb483762a491174d0eaf733
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 7341b68328712adcbbb7b28b9c83695a56fcaac29eb9d7303c8866cf0f6004bd7e85065a1684a3acb1597786c1993b876e8eab04f5db555d95563f43976e7934
|
7
|
+
data.tar.gz: 8b8f93c545f0cc80b3ee36610751db469c1cf6c47df4ba9376189c926480dc0a96d678545c90163f2f24f1826beeaaff1e24ec9bc273a24541e715b0701ceb61
|
data/README.md
CHANGED
@@ -24,7 +24,7 @@ i18n-tasks can be used with any project using the ruby [i18n gem][i18n-gem] (def
|
|
24
24
|
Add i18n-tasks to the Gemfile:
|
25
25
|
|
26
26
|
```ruby
|
27
|
-
gem 'i18n-tasks', '~> 1.0.
|
27
|
+
gem 'i18n-tasks', '~> 1.0.11'
|
28
28
|
```
|
29
29
|
|
30
30
|
Copy the default [configuration file](#configuration):
|
@@ -355,7 +355,7 @@ If you have implemented a custom adapter please share it on [the wiki][wiki].
|
|
355
355
|
|
356
356
|
### Usage search
|
357
357
|
|
358
|
-
i18n-tasks uses an AST scanner for `.rb` files, and a regexp scanner for all other files.
|
358
|
+
i18n-tasks uses an AST scanner for `.rb` and `.html.erb` files, and a regexp scanner for all other files.
|
359
359
|
New scanners can be added easily: please refer to [this example](https://github.com/glebm/i18n-tasks/wiki/A-custom-scanner-example).
|
360
360
|
|
361
361
|
See the `search` section in the [config file][config] for all available configuration options.
|
@@ -436,15 +436,28 @@ See [i18n-tasks wiki: CSV import and export tasks](https://github.com/glebm/i18n
|
|
436
436
|
Tasks that come with the gem are defined in [lib/i18n/tasks/command/commands](lib/i18n/tasks/command/commands).
|
437
437
|
Custom tasks can be added easily, see the examples [on the wiki](https://github.com/glebm/i18n-tasks/wiki#custom-tasks).
|
438
438
|
|
439
|
+
# Development
|
440
|
+
|
441
|
+
- Install dependencies using `bundle install`
|
442
|
+
- Run tests using `bundle exec rspec`
|
443
|
+
- Install [Overcommit](overcommit) by running `overcommit --install`
|
444
|
+
|
445
|
+
## Skip Overcommit-hooks
|
446
|
+
|
447
|
+
- `SKIP=RuboCop git commit`
|
448
|
+
- `OVERCOMMIT_DISABLE=1 git commit`
|
449
|
+
|
450
|
+
|
439
451
|
[MIT license]: /LICENSE.txt
|
440
452
|
[ci]: https://github.com/glebm/i18n-tasks/actions/workflows/tests.yml
|
441
453
|
[badge-ci]: https://github.com/glebm/i18n-tasks/actions/workflows/tests.yml/badge.svg
|
442
454
|
[coverage]: https://codeclimate.com/github/glebm/i18n-tasks
|
443
455
|
[badge-coverage]: https://api.codeclimate.com/v1/badges/5d173e90ada8df07cedc/test_coverage
|
444
|
-
[config]: https://github.com/glebm/i18n-tasks/blob/
|
456
|
+
[config]: https://github.com/glebm/i18n-tasks/blob/main/templates/config/i18n-tasks.yml
|
445
457
|
[wiki]: https://github.com/glebm/i18n-tasks/wiki "i18n-tasks wiki"
|
446
458
|
[i18n-gem]: https://github.com/svenfuchs/i18n "svenfuchs/i18n on Github"
|
447
459
|
[screenshot-i18n-tasks]: https://i.imgur.com/XZBd8l7.png "i18n-tasks screenshot"
|
448
460
|
[screenshot-find]: https://i.imgur.com/VxBrSfY.png "i18n-tasks find output screenshot"
|
449
|
-
[adapter-example]: https://github.com/glebm/i18n-tasks/blob/
|
461
|
+
[adapter-example]: https://github.com/glebm/i18n-tasks/blob/main/lib/i18n/tasks/data/file_system_base.rb
|
450
462
|
[custom-scanner-docs]: https://github.com/glebm/i18n-tasks/wiki/A-custom-scanner-example
|
463
|
+
[overcommit]: https://github.com/sds/overcommit#installation
|
data/i18n-tasks.gemspec
CHANGED
@@ -22,10 +22,15 @@ Gem::Specification.new do |s| # rubocop:disable Metrics/BlockLength
|
|
22
22
|
cp $(bundle exec i18n-tasks gem-path)/templates/config/i18n-tasks.yml config/
|
23
23
|
# Add an RSpec for missing and unused keys:
|
24
24
|
cp $(bundle exec i18n-tasks gem-path)/templates/rspec/i18n_spec.rb spec/
|
25
|
+
# Or for minitest:
|
26
|
+
cp $(bundle exec i18n-tasks gem-path)/templates/minitest/i18n_test.rb test/
|
25
27
|
TEXT
|
26
28
|
s.homepage = 'https://github.com/glebm/i18n-tasks'
|
27
|
-
s.metadata = {
|
28
|
-
|
29
|
+
s.metadata = {
|
30
|
+
'issue_tracker' => 'https://github.com/glebm/i18n-tasks',
|
31
|
+
'rubygems_mfa_required' => 'true'
|
32
|
+
}
|
33
|
+
s.required_ruby_version = '>= 2.6', '< 4.0'
|
29
34
|
|
30
35
|
s.files = `git ls-files`.split($/)
|
31
36
|
s.files -= s.files.grep(%r{^(doc/|\.|spec/)}) + %w[CHANGES.md config/i18n-tasks.yml Gemfile]
|
@@ -43,11 +48,11 @@ Gem::Specification.new do |s| # rubocop:disable Metrics/BlockLength
|
|
43
48
|
s.add_dependency 'rails-i18n'
|
44
49
|
s.add_dependency 'rainbow', '>= 2.2.2', '< 4.0'
|
45
50
|
s.add_dependency 'terminal-table', '>= 1.5.1'
|
46
|
-
s.add_development_dependency 'axlsx', '~> 2.0'
|
47
51
|
s.add_development_dependency 'bundler', '~> 2.0', '>= 2.0.1'
|
52
|
+
s.add_development_dependency 'overcommit', '~> 0.58.0'
|
48
53
|
s.add_development_dependency 'rake'
|
49
54
|
s.add_development_dependency 'rspec', '~> 3.3'
|
50
|
-
s.add_development_dependency 'rubocop', '~> 1.
|
55
|
+
s.add_development_dependency 'rubocop', '~> 1.27.0'
|
51
56
|
s.add_development_dependency 'simplecov'
|
52
57
|
s.add_development_dependency 'yard'
|
53
58
|
|
data/lib/i18n/tasks/cli.rb
CHANGED
@@ -35,12 +35,13 @@ class I18n::Tasks::CLI
|
|
35
35
|
|
36
36
|
def run(argv)
|
37
37
|
argv.each_with_index do |arg, i|
|
38
|
-
if [
|
39
|
-
|
40
|
-
|
38
|
+
if ['--config', '-c'].include?(arg)
|
39
|
+
_, config_file = argv.slice!(i, 2)
|
40
|
+
if File.exist?(config_file)
|
41
|
+
@config_file = config_file
|
41
42
|
break
|
42
43
|
else
|
43
|
-
error "Config file doesn't exist: #{
|
44
|
+
error "Config file doesn't exist: #{config_file}", 128
|
44
45
|
end
|
45
46
|
end
|
46
47
|
end
|
@@ -86,7 +86,7 @@ module I18n::Tasks
|
|
86
86
|
when 'keys'
|
87
87
|
puts forest.key_names(root: true)
|
88
88
|
when 'key-values'
|
89
|
-
puts
|
89
|
+
puts(forest.key_values(root: true).map { |kv| kv.join("\t") })
|
90
90
|
when *DATA_FORMATS
|
91
91
|
puts i18n.data.adapter_dump forest.to_hash(true), format
|
92
92
|
end
|
@@ -30,6 +30,13 @@ module I18n::Tasks::Configuration # rubocop:disable Metrics/ModuleLength
|
|
30
30
|
warn_deprecated 'Please move relative_roots under search in config/i18n-tasks.yml.'
|
31
31
|
c[:search][:relative_roots] = c.delete(:relative_roots)
|
32
32
|
end
|
33
|
+
|
34
|
+
if c.dig(:search, :exclude_method_name_paths)
|
35
|
+
warn_deprecated(
|
36
|
+
'Please rename exclude_method_name_paths to relative_exclude_method_name_paths in config/i18n-tasks.yml.'
|
37
|
+
)
|
38
|
+
c[:search][:relative_exclude_method_name_paths] = c[:search].delete(:exclude_method_name_paths)
|
39
|
+
end
|
33
40
|
end
|
34
41
|
else
|
35
42
|
{}.with_indifferent_access
|
@@ -44,12 +51,10 @@ module I18n::Tasks::Configuration # rubocop:disable Metrics/ModuleLength
|
|
44
51
|
# data config
|
45
52
|
# @return [Hash<adapter: String, options: Hash>]
|
46
53
|
def data_config
|
47
|
-
@config_sections[:data] ||=
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
}
|
52
|
-
end
|
54
|
+
@config_sections[:data] ||= {
|
55
|
+
adapter: data.class.name,
|
56
|
+
config: data.config
|
57
|
+
}
|
53
58
|
end
|
54
59
|
|
55
60
|
# translation config
|
@@ -5,7 +5,7 @@ module I18n::Tasks
|
|
5
5
|
module Data
|
6
6
|
module Adapter
|
7
7
|
module YamlAdapter
|
8
|
-
EMOJI_REGEX = /\\u[\da-f]{8}/i
|
8
|
+
EMOJI_REGEX = /\\u[\da-f]{8}/i.freeze
|
9
9
|
|
10
10
|
class << self
|
11
11
|
# @return [Hash] locale tree
|
@@ -25,7 +25,7 @@ module I18n::Tasks
|
|
25
25
|
|
26
26
|
# @return [String]
|
27
27
|
def restore_emojis(yaml)
|
28
|
-
yaml.gsub(EMOJI_REGEX) { |m| [m[-8..].to_i(16)].pack(
|
28
|
+
yaml.gsub(EMOJI_REGEX) { |m| [m[-8..].to_i(16)].pack('U') }
|
29
29
|
end
|
30
30
|
end
|
31
31
|
end
|
@@ -38,7 +38,7 @@ module I18n::Tasks
|
|
38
38
|
if pattern
|
39
39
|
key_match = $~
|
40
40
|
path = format(path, locale: locale)
|
41
|
-
path.gsub!(/\\\d+/) { |m| key_match[m[1
|
41
|
+
path.gsub!(/\\\d+/) { |m| key_match[m[1..].to_i] }
|
42
42
|
(out[path] ||= Set.new) << "#{locale}.#{key}"
|
43
43
|
else
|
44
44
|
fail CommandError, "Cannot route key #{key}. Routes are #{@routes_config.inspect}"
|
@@ -168,7 +168,7 @@ module I18n::Tasks::Data::Tree
|
|
168
168
|
label = if key.nil?
|
169
169
|
Rainbow('∅').faint
|
170
170
|
else
|
171
|
-
[Rainbow(key).color(1 + level % 15),
|
171
|
+
[Rainbow(key).color(1 + (level % 15)),
|
172
172
|
(": #{format_value_for_inspect(value)}" if leaf?),
|
173
173
|
(" #{data}" if data?)].compact.join
|
174
174
|
end
|
@@ -31,13 +31,11 @@ module I18n::Tasks::Data::Tree
|
|
31
31
|
end
|
32
32
|
|
33
33
|
def to_hash(sort = false)
|
34
|
-
(@hash ||= {})[sort] ||=
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
end.map { |node| node.to_hash(sort) }.reduce({}, :deep_merge!)
|
40
|
-
end
|
34
|
+
(@hash ||= {})[sort] ||= if sort
|
35
|
+
sort_by(&:key)
|
36
|
+
else
|
37
|
+
self
|
38
|
+
end.map { |node| node.to_hash(sort) }.reduce({}, :deep_merge!)
|
41
39
|
end
|
42
40
|
|
43
41
|
delegate :to_json, to: :to_hash
|
@@ -3,7 +3,6 @@
|
|
3
3
|
require 'set'
|
4
4
|
require 'i18n/tasks/split_key'
|
5
5
|
require 'i18n/tasks/data/tree/nodes'
|
6
|
-
require 'i18n/tasks/data/tree/node'
|
7
6
|
|
8
7
|
module I18n::Tasks::Data::Tree
|
9
8
|
# Siblings represents a subtree sharing a common parent
|
@@ -50,7 +49,7 @@ module I18n::Tasks::Data::Tree
|
|
50
49
|
next
|
51
50
|
end
|
52
51
|
match = $~
|
53
|
-
new_key = to_pattern.gsub(/\\\d+/) { |m| match[m[1
|
52
|
+
new_key = to_pattern.gsub(/\\\d+/) { |m| match[m[1..].to_i] }
|
54
53
|
old_key_to_new_key[full_key] = new_key
|
55
54
|
moved_forest.merge!(Siblings.new.tap do |forest|
|
56
55
|
forest[[(node.root.try(:key) unless root), new_key].compact.join('.')] =
|
data/lib/i18n/tasks/html_keys.rb
CHANGED
@@ -7,8 +7,8 @@ module I18n::Tasks
|
|
7
7
|
|
8
8
|
def html_key?(full_key, locale)
|
9
9
|
!!(full_key =~ HTML_KEY_PATTERN ||
|
10
|
-
full_key =~ MAYBE_PLURAL_HTML_KEY_PATTERN &&
|
11
|
-
depluralize_key(split_key(full_key, 2)[1], locale) =~ HTML_KEY_PATTERN)
|
10
|
+
(full_key =~ MAYBE_PLURAL_HTML_KEY_PATTERN &&
|
11
|
+
depluralize_key(split_key(full_key, 2)[1], locale) =~ HTML_KEY_PATTERN))
|
12
12
|
end
|
13
13
|
end
|
14
14
|
end
|
@@ -91,8 +91,8 @@ module I18n::Tasks
|
|
91
91
|
if node.value != other.value
|
92
92
|
log_warn(
|
93
93
|
'Conflicting references: '\
|
94
|
-
|
95
|
-
|
94
|
+
"#{node.full_key(root: false)} ⮕ #{node.value} in #{node.data[:locale]},"\
|
95
|
+
" but ⮕ #{other.value} in #{other.data[:locale]}"
|
96
96
|
)
|
97
97
|
end
|
98
98
|
end
|
@@ -37,7 +37,7 @@ module I18n::Tasks::Reports
|
|
37
37
|
def used_title(keys_nodes, filter)
|
38
38
|
used_n = keys_nodes.map { |_k, node| node.data[:occurrences].size }.reduce(:+).to_i
|
39
39
|
"#{keys_nodes.size} key#{'s' if keys_nodes.size != 1}#{" matching '#{filter}'" if filter}"\
|
40
|
-
|
40
|
+
"#{" (#{used_n} usage#{'s' if used_n != 1})" if used_n.positive?}"
|
41
41
|
end
|
42
42
|
|
43
43
|
# Sort keys by their attributes in order
|
@@ -121,9 +121,9 @@ module I18n
|
|
121
121
|
if data[:ref_info]
|
122
122
|
from, to = data[:ref_info]
|
123
123
|
resolved = key[0...to.length]
|
124
|
-
after = key[to.length
|
124
|
+
after = key[to.length..]
|
125
125
|
" #{Rainbow(from).yellow}#{Rainbow(after).cyan}\n" \
|
126
|
-
|
126
|
+
"#{Rainbow('⮕').yellow.bright} #{Rainbow(resolved).yellow.bright}"
|
127
127
|
else
|
128
128
|
Rainbow(key).cyan
|
129
129
|
end
|
@@ -6,6 +6,10 @@ module I18n::Tasks::Scanners::AstMatchers
|
|
6
6
|
@scanner = scanner
|
7
7
|
end
|
8
8
|
|
9
|
+
def convert_to_key_occurrences(send_node, _method_name, location: send_node.loc)
|
10
|
+
fail('Not implemented')
|
11
|
+
end
|
12
|
+
|
9
13
|
protected
|
10
14
|
|
11
15
|
# If the node type is of `%i(sym str int false true)`, return the value as a string.
|
@@ -20,7 +24,7 @@ module I18n::Tasks::Scanners::AstMatchers
|
|
20
24
|
# No effect unless `array_join_with` is set.
|
21
25
|
# @return [String, nil] `nil` is returned only when a dynamic value is encountered in strict mode
|
22
26
|
# or the node type is not supported.
|
23
|
-
def extract_string(node, array_join_with: nil, array_flatten: false, array_reject_blank: false)
|
27
|
+
def extract_string(node, array_join_with: nil, array_flatten: false, array_reject_blank: false) # rubocop:disable Metrics/AbcSize, Metrics/PerceivedComplexity
|
24
28
|
return if node.nil?
|
25
29
|
|
26
30
|
if %i[sym str int].include?(node.type)
|
@@ -0,0 +1,69 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'i18n/tasks/scanners/results/occurrence'
|
4
|
+
|
5
|
+
module I18n::Tasks::Scanners::AstMatchers
|
6
|
+
class RailsModelMatcher < BaseMatcher
|
7
|
+
def convert_to_key_occurrences(send_node, _method_name, location: send_node.loc)
|
8
|
+
human_attribute_name_to_key_occurences(send_node: send_node, location: location) ||
|
9
|
+
model_name_human_to_key_occurences(send_node: send_node, location: location)
|
10
|
+
end
|
11
|
+
|
12
|
+
private
|
13
|
+
|
14
|
+
def human_attribute_name_to_key_occurences(send_node:, location:)
|
15
|
+
children = Array(send_node&.children)
|
16
|
+
receiver = children[0]
|
17
|
+
method_name = children[1]
|
18
|
+
|
19
|
+
return unless method_name == :human_attribute_name && receiver.type == :const
|
20
|
+
|
21
|
+
value = children[2]
|
22
|
+
|
23
|
+
model_name = underscore(receiver.to_a.last)
|
24
|
+
attribute = extract_string(value)
|
25
|
+
key = "activerecord.attributes.#{model_name}.#{attribute}"
|
26
|
+
[
|
27
|
+
key,
|
28
|
+
I18n::Tasks::Scanners::Results::Occurrence.from_range(
|
29
|
+
raw_key: key,
|
30
|
+
range: location.expression
|
31
|
+
)
|
32
|
+
]
|
33
|
+
end
|
34
|
+
|
35
|
+
# User.model_name.human(count: 2)
|
36
|
+
# s(:send,
|
37
|
+
# s(:send,
|
38
|
+
# s(:const, nil, :User), :model_name), :human,
|
39
|
+
# s(:hash,
|
40
|
+
# s(:pair,
|
41
|
+
# s(:sym, :count),
|
42
|
+
# s(:int, 2))))
|
43
|
+
def model_name_human_to_key_occurences(send_node:, location:)
|
44
|
+
children = Array(send_node&.children)
|
45
|
+
return unless children[1] == :human
|
46
|
+
|
47
|
+
base_children = Array(children[0]&.children)
|
48
|
+
class_node = base_children[0]
|
49
|
+
|
50
|
+
return unless class_node&.type == :const && base_children[1] == :model_name
|
51
|
+
|
52
|
+
model_name = underscore(class_node.to_a.last)
|
53
|
+
key = "activerecord.models.#{model_name}"
|
54
|
+
[
|
55
|
+
key,
|
56
|
+
I18n::Tasks::Scanners::Results::Occurrence.from_range(
|
57
|
+
raw_key: key,
|
58
|
+
range: location.expression
|
59
|
+
)
|
60
|
+
]
|
61
|
+
end
|
62
|
+
|
63
|
+
def underscore(value)
|
64
|
+
value = value.dup.to_s
|
65
|
+
value.gsub!(/(.)([A-Z])/, '\1_\2')
|
66
|
+
value.downcase!
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
@@ -35,7 +35,7 @@ module I18n::Tasks::Scanners
|
|
35
35
|
result = []
|
36
36
|
text.scan(pattern) do |_|
|
37
37
|
match = Regexp.last_match
|
38
|
-
matches =
|
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])
|
@@ -10,7 +10,7 @@ module I18n
|
|
10
10
|
# @param calling_method [#call, Symbol, String, false, nil]
|
11
11
|
# @return [String] absolute version of the key
|
12
12
|
def absolute_key(key, path, roots: config[:relative_roots],
|
13
|
-
exclude_method_name_paths: config[:
|
13
|
+
exclude_method_name_paths: config[:relative_exclude_method_name_paths],
|
14
14
|
calling_method: nil)
|
15
15
|
return key unless key.start_with?(DOT)
|
16
16
|
fail 'roots argument is required' unless roots.present?
|
@@ -18,7 +18,7 @@ module I18n
|
|
18
18
|
normalized_path = File.expand_path(path)
|
19
19
|
(root = path_root(normalized_path, roots)) ||
|
20
20
|
fail(CommandError, "Cannot resolve relative key \"#{key}\".\n" \
|
21
|
-
|
21
|
+
"Set search.relative_roots in config/i18n-tasks.yml (currently #{roots.inspect})")
|
22
22
|
normalized_path.sub!(root, '')
|
23
23
|
|
24
24
|
if (exclude_method_name_paths || []).map { |p| expand_path(p) }.include?(root)
|
@@ -48,7 +48,7 @@ module I18n::Tasks
|
|
48
48
|
# rubocop:enable Metrics/ParameterLists
|
49
49
|
|
50
50
|
def inspect
|
51
|
-
"Occurrence(#{@path}:#{@line_num}, line_pos: #{@line_pos}, pos: #{@pos}, raw_key: #{@raw_key}, default_arg: #{@default_arg}, line: #{@line})"
|
51
|
+
"Occurrence(#{@path}:#{@line_num}, line_pos: #{@line_pos}, pos: #{@pos}, raw_key: #{@raw_key}, default_arg: #{@default_arg}, line: #{@line})" # rubocop:disable Layout/LineLength
|
52
52
|
end
|
53
53
|
|
54
54
|
def ==(other)
|
@@ -68,8 +68,7 @@ module I18n::Tasks::Scanners
|
|
68
68
|
def comments_to_occurences(path, ast, comments)
|
69
69
|
magic_comments = comments.select { |comment| comment.text =~ MAGIC_COMMENT_PREFIX }
|
70
70
|
comment_to_node = Parser::Source::Comment.associate_locations(ast, magic_comments).tap do |h|
|
71
|
-
|
72
|
-
h.each { |k, v| h[k] = v.first }
|
71
|
+
h.transform_values!(&:first)
|
73
72
|
end.invert
|
74
73
|
|
75
74
|
magic_comments.flat_map do |comment|
|
@@ -122,7 +121,7 @@ module I18n::Tasks::Scanners
|
|
122
121
|
)
|
123
122
|
end
|
124
123
|
else
|
125
|
-
%i[t t! translate translate!].map do |message|
|
124
|
+
matchers = %i[t t! translate translate!].map do |message|
|
126
125
|
AstMatchers::MessageReceiversMatcher.new(
|
127
126
|
receivers: [
|
128
127
|
AST::Node.new(:const, [nil, :I18n]),
|
@@ -132,6 +131,12 @@ module I18n::Tasks::Scanners
|
|
132
131
|
scanner: self
|
133
132
|
)
|
134
133
|
end
|
134
|
+
|
135
|
+
Array(config[:ast_matchers]).each do |class_name|
|
136
|
+
matchers << ActiveSupport::Inflector.constantize(class_name).new(scanner: self)
|
137
|
+
end
|
138
|
+
|
139
|
+
matchers
|
135
140
|
end
|
136
141
|
end
|
137
142
|
end
|
@@ -15,12 +15,12 @@ module I18n::Tasks::Scanners
|
|
15
15
|
# @param literal [String] e.g: "key", 'key', or :key.
|
16
16
|
# @return [String] key
|
17
17
|
def strip_literal(literal)
|
18
|
-
literal = literal[1
|
18
|
+
literal = literal[1..] if literal[0] == ':'
|
19
19
|
literal = literal[1..-2] if literal[0] == "'" || literal[0] == '"'
|
20
20
|
literal
|
21
21
|
end
|
22
22
|
|
23
|
-
VALID_KEY_CHARS =
|
23
|
+
VALID_KEY_CHARS = %r{(?:[[:word:]]|[-.?!:;À-ž/])}.freeze
|
24
24
|
VALID_KEY_RE = /^#{VALID_KEY_CHARS}+$/.freeze
|
25
25
|
|
26
26
|
def valid_key?(key)
|
data/lib/i18n/tasks/split_key.rb
CHANGED
@@ -114,7 +114,7 @@ module I18n::Tasks
|
|
114
114
|
|
115
115
|
values = untranslated.scan(INTERPOLATION_KEY_RE)
|
116
116
|
translated.gsub(/#{Regexp.escape(UNTRANSLATABLE_STRING)}\d+/i) do |m|
|
117
|
-
values[m[UNTRANSLATABLE_STRING.length
|
117
|
+
values[m[UNTRANSLATABLE_STRING.length..].to_i]
|
118
118
|
end
|
119
119
|
rescue StandardError => e
|
120
120
|
raise_interpolation_error(untranslated, translated, e)
|
@@ -71,11 +71,11 @@ module I18n::Tasks::Translators
|
|
71
71
|
version = @i18n_tasks.translation_config[:deepl_version]
|
72
72
|
fail ::I18n::Tasks::CommandError, I18n.t('i18n_tasks.deepl_translate.errors.no_api_key') if api_key.blank?
|
73
73
|
|
74
|
-
DeepL.configure
|
74
|
+
DeepL.configure do |config|
|
75
75
|
config.auth_key = api_key
|
76
76
|
config.host = host unless host.blank?
|
77
77
|
config.version = version unless version.blank?
|
78
|
-
|
78
|
+
end
|
79
79
|
end
|
80
80
|
end
|
81
81
|
end
|
data/lib/i18n/tasks/used_keys.rb
CHANGED
@@ -24,12 +24,13 @@ module I18n::Tasks
|
|
24
24
|
['::I18n::Tasks::Scanners::ErbAstScanner', { only: %w[*.erb] }],
|
25
25
|
['::I18n::Tasks::Scanners::PatternWithScopeScanner', { exclude: %w[*.erb *.rb] }]
|
26
26
|
],
|
27
|
+
ast_matchers: [],
|
27
28
|
strict: true
|
28
29
|
}.freeze
|
29
30
|
|
30
31
|
ALWAYS_EXCLUDE = %w[*.jpg *.jpeg *.png *.gif *.svg *.ico *.eot *.otf *.ttf *.woff *.woff2 *.pdf *.css *.sass *.scss *.less
|
31
32
|
*.yml *.json *.zip *.tar.gz *.swf *.flv *.mp3 *.wav *.flac *.webm *.mp4 *.ogg *.opus
|
32
|
-
*.webp *.map].freeze
|
33
|
+
*.webp *.map *.xlsx].freeze
|
33
34
|
|
34
35
|
# Find all keys in the source and return a forest with the keys in absolute form and their occurrences.
|
35
36
|
#
|
@@ -177,7 +178,7 @@ module I18n::Tasks
|
|
177
178
|
braces << '{'
|
178
179
|
end
|
179
180
|
end
|
180
|
-
result << key[scanner.pos
|
181
|
+
result << key[scanner.pos..] unless scanner.eos?
|
181
182
|
result.join
|
182
183
|
end
|
183
184
|
end
|
data/lib/i18n/tasks/version.rb
CHANGED
data/lib/i18n/tasks.rb
CHANGED
@@ -34,6 +34,17 @@ module I18n
|
|
34
34
|
::I18n::Tasks::Commands.send :include, commands_module
|
35
35
|
self
|
36
36
|
end
|
37
|
+
|
38
|
+
# Add AST-matcher to i18n-tasks
|
39
|
+
#
|
40
|
+
# @param matcher_class_name
|
41
|
+
# @return self
|
42
|
+
def add_ast_matcher(matcher_class_name)
|
43
|
+
matchers = I18n::Tasks::Configuration::DEFAULTS[:search][:ast_matchers]
|
44
|
+
matchers << matcher_class_name
|
45
|
+
matchers.uniq!
|
46
|
+
self
|
47
|
+
end
|
37
48
|
end
|
38
49
|
|
39
50
|
@verbose = !ENV['VERBOSE'].nil?
|
@@ -72,11 +72,13 @@ search:
|
|
72
72
|
# -
|
73
73
|
|
74
74
|
## Files or `File.fnmatch` patterns to exclude from search. Some files are always excluded regardless of this setting:
|
75
|
-
##
|
75
|
+
## *.jpg *.jpeg *.png *.gif *.svg *.ico *.eot *.otf *.ttf *.woff *.woff2 *.pdf *.css *.sass *.scss *.less
|
76
|
+
## *.yml *.json *.zip *.tar.gz *.swf *.flv *.mp3 *.wav *.flac *.webm *.mp4 *.ogg *.opus *.webp *.map *.xlsx
|
76
77
|
exclude:
|
77
78
|
- app/assets/images
|
78
79
|
- app/assets/fonts
|
79
80
|
- app/assets/videos
|
81
|
+
- app/assets/builds
|
80
82
|
|
81
83
|
## Alternatively, the only files or `File.fnmatch patterns` to search in `paths`:
|
82
84
|
## If specified, this settings takes priority over `exclude`, but `exclude` still applies.
|
@@ -85,6 +87,15 @@ search:
|
|
85
87
|
## If `strict` is `false`, guess usages such as t("categories.#{category}.title"). The default is `true`.
|
86
88
|
# strict: true
|
87
89
|
|
90
|
+
## Allows adding ast_matchers for finding translations using the AST-scanners
|
91
|
+
## The available matchers are:
|
92
|
+
## - RailsModelMatcher
|
93
|
+
## Matches ActiveRecord translations like
|
94
|
+
## User.human_attribute_name(:email) and User.model_name.human
|
95
|
+
##
|
96
|
+
## To implement your own, please see `I18n::Tasks::Scanners::AstMatchers::BaseMatcher`.
|
97
|
+
<%# I18n::Tasks.add_ast_matcher('I18n::Tasks::Scanners::AstMatchers::RailsModelMatcher') %>
|
98
|
+
|
88
99
|
## Multiple scanners can be used. Their results are merged.
|
89
100
|
## The options specified above are passed down to each scanner. Per-scanner options can be specified as well.
|
90
101
|
## See this example of a custom scanner: https://github.com/glebm/i18n-tasks/wiki/A-custom-scanner-example
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: i18n-tasks
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.0.
|
4
|
+
version: 1.0.11
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- glebm
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2022-
|
11
|
+
date: 2022-06-15 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activesupport
|
@@ -157,12 +157,15 @@ dependencies:
|
|
157
157
|
- !ruby/object:Gem::Version
|
158
158
|
version: 1.5.1
|
159
159
|
- !ruby/object:Gem::Dependency
|
160
|
-
name:
|
160
|
+
name: bundler
|
161
161
|
requirement: !ruby/object:Gem::Requirement
|
162
162
|
requirements:
|
163
163
|
- - "~>"
|
164
164
|
- !ruby/object:Gem::Version
|
165
165
|
version: '2.0'
|
166
|
+
- - ">="
|
167
|
+
- !ruby/object:Gem::Version
|
168
|
+
version: 2.0.1
|
166
169
|
type: :development
|
167
170
|
prerelease: false
|
168
171
|
version_requirements: !ruby/object:Gem::Requirement
|
@@ -170,26 +173,23 @@ dependencies:
|
|
170
173
|
- - "~>"
|
171
174
|
- !ruby/object:Gem::Version
|
172
175
|
version: '2.0'
|
176
|
+
- - ">="
|
177
|
+
- !ruby/object:Gem::Version
|
178
|
+
version: 2.0.1
|
173
179
|
- !ruby/object:Gem::Dependency
|
174
|
-
name:
|
180
|
+
name: overcommit
|
175
181
|
requirement: !ruby/object:Gem::Requirement
|
176
182
|
requirements:
|
177
183
|
- - "~>"
|
178
184
|
- !ruby/object:Gem::Version
|
179
|
-
version:
|
180
|
-
- - ">="
|
181
|
-
- !ruby/object:Gem::Version
|
182
|
-
version: 2.0.1
|
185
|
+
version: 0.58.0
|
183
186
|
type: :development
|
184
187
|
prerelease: false
|
185
188
|
version_requirements: !ruby/object:Gem::Requirement
|
186
189
|
requirements:
|
187
190
|
- - "~>"
|
188
191
|
- !ruby/object:Gem::Version
|
189
|
-
version:
|
190
|
-
- - ">="
|
191
|
-
- !ruby/object:Gem::Version
|
192
|
-
version: 2.0.1
|
192
|
+
version: 0.58.0
|
193
193
|
- !ruby/object:Gem::Dependency
|
194
194
|
name: rake
|
195
195
|
requirement: !ruby/object:Gem::Requirement
|
@@ -224,14 +224,14 @@ dependencies:
|
|
224
224
|
requirements:
|
225
225
|
- - "~>"
|
226
226
|
- !ruby/object:Gem::Version
|
227
|
-
version: 1.
|
227
|
+
version: 1.27.0
|
228
228
|
type: :development
|
229
229
|
prerelease: false
|
230
230
|
version_requirements: !ruby/object:Gem::Requirement
|
231
231
|
requirements:
|
232
232
|
- - "~>"
|
233
233
|
- !ruby/object:Gem::Version
|
234
|
-
version: 1.
|
234
|
+
version: 1.27.0
|
235
235
|
- !ruby/object:Gem::Dependency
|
236
236
|
name: simplecov
|
237
237
|
requirement: !ruby/object:Gem::Requirement
|
@@ -373,6 +373,7 @@ files:
|
|
373
373
|
- lib/i18n/tasks/reports/terminal.rb
|
374
374
|
- lib/i18n/tasks/scanners/ast_matchers/base_matcher.rb
|
375
375
|
- lib/i18n/tasks/scanners/ast_matchers/message_receivers_matcher.rb
|
376
|
+
- lib/i18n/tasks/scanners/ast_matchers/rails_model_matcher.rb
|
376
377
|
- lib/i18n/tasks/scanners/erb_ast_processor.rb
|
377
378
|
- lib/i18n/tasks/scanners/erb_ast_scanner.rb
|
378
379
|
- lib/i18n/tasks/scanners/file_scanner.rb
|
@@ -413,11 +414,14 @@ licenses:
|
|
413
414
|
- MIT
|
414
415
|
metadata:
|
415
416
|
issue_tracker: https://github.com/glebm/i18n-tasks
|
417
|
+
rubygems_mfa_required: 'true'
|
416
418
|
post_install_message: |
|
417
419
|
# Install default configuration:
|
418
420
|
cp $(bundle exec i18n-tasks gem-path)/templates/config/i18n-tasks.yml config/
|
419
421
|
# Add an RSpec for missing and unused keys:
|
420
422
|
cp $(bundle exec i18n-tasks gem-path)/templates/rspec/i18n_spec.rb spec/
|
423
|
+
# Or for minitest:
|
424
|
+
cp $(bundle exec i18n-tasks gem-path)/templates/minitest/i18n_test.rb test/
|
421
425
|
rdoc_options: []
|
422
426
|
require_paths:
|
423
427
|
- lib
|