i18n-tasks 0.4.5 → 0.5.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.travis.yml +0 -4
- data/CHANGES.md +7 -0
- data/README.md +10 -14
- data/i18n-tasks.gemspec +1 -1
- data/lib/i18n/tasks.rb +0 -2
- data/lib/i18n/tasks/base_task.rb +4 -2
- data/lib/i18n/tasks/commands.rb +14 -14
- data/lib/i18n/tasks/configuration.rb +10 -2
- data/lib/i18n/tasks/console_context.rb +73 -0
- data/lib/i18n/tasks/data.rb +0 -47
- data/lib/i18n/tasks/data/adapter/yaml_adapter.rb +6 -1
- data/lib/i18n/tasks/data/file_system_base.rb +1 -1
- data/lib/i18n/tasks/data/router/conservative_router.rb +5 -5
- data/lib/i18n/tasks/data/router/pattern_router.rb +2 -2
- data/lib/i18n/tasks/data/tree/node.rb +47 -36
- data/lib/i18n/tasks/data/tree/nodes.rb +0 -4
- data/lib/i18n/tasks/data/tree/siblings.rb +54 -9
- data/lib/i18n/tasks/data/tree/traversal.rb +62 -23
- data/lib/i18n/tasks/fill_tasks.rb +29 -21
- data/lib/i18n/tasks/ignore_keys.rb +1 -1
- data/lib/i18n/tasks/key_pattern_matching.rb +17 -0
- data/lib/i18n/tasks/missing_keys.rb +39 -44
- data/lib/i18n/tasks/plural_keys.rb +14 -1
- data/lib/i18n/tasks/reports/base.rb +28 -8
- data/lib/i18n/tasks/reports/spreadsheet.rb +9 -8
- data/lib/i18n/tasks/reports/terminal.rb +33 -29
- data/lib/i18n/tasks/scanners/base_scanner.rb +22 -14
- data/lib/i18n/tasks/scanners/pattern_scanner.rb +2 -1
- data/lib/i18n/tasks/unused_keys.rb +13 -13
- data/lib/i18n/tasks/used_keys.rb +39 -38
- data/lib/i18n/tasks/version.rb +1 -1
- data/spec/i18n_tasks_spec.rb +41 -40
- data/spec/locale_tree/siblings_spec.rb +26 -1
- data/spec/support/i18n_tasks_output_matcher.rb +4 -1
- data/spec/support/trees.rb +6 -1
- data/spec/used_keys_spec.rb +23 -15
- metadata +4 -11
- data/lib/i18n/tasks/file_structure.rb +0 -19
- data/lib/i18n/tasks/key.rb +0 -48
- data/lib/i18n/tasks/key/key_group.rb +0 -45
- data/lib/i18n/tasks/key/match_pattern.rb +0 -24
- data/lib/i18n/tasks/key/usages.rb +0 -12
- data/lib/i18n/tasks/key_group.rb +0 -68
- data/spec/key_group_spec.rb +0 -49
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f4ae19f0f97a5f8ae9221b45ef316b08a5efbe62
|
4
|
+
data.tar.gz: 2b957a1c692318da437ca4235aeb2a1b9e47f2b6
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d00e68fb5727c9d95df84b5de56bd57d53a6adc6e9e00e1ce05c16e6f99055b30b0bc9c478dfabba238962a5d75cc75dc75b97ae4724bde1b07e4f07150b396d
|
7
|
+
data.tar.gz: 5bc8932e731220f37abce28f4b637c60ab41ed02b8e3c8ab5f0c273385ae269fae4a996e408a006d9ba59bd95bab1e1599d2cc48670ff828421fe96d4d1380d4
|
data/.travis.yml
CHANGED
data/CHANGES.md
CHANGED
data/README.md
CHANGED
@@ -1,12 +1,12 @@
|
|
1
1
|
# i18n-tasks [![Build Status][badge-travis]][travis] [![Coverage Status][badge-coveralls]][coveralls] [![Code Climate][badge-code-climate]][code-climate] [![Gemnasium][badge-gemnasium]][gemnasium]
|
2
2
|
|
3
|
-
i18n-tasks
|
3
|
+
i18n-tasks helps you find and manage missing and unused translations.
|
4
4
|
|
5
5
|
The default approach to locale data management with gems such as [i18n][i18n-gem] is flawed.
|
6
6
|
If you use a key that does not exist, this will only blow up at runtime. Keys left over from removed code accumulate
|
7
7
|
in the resource files and introduce unnecessary overhead on the translators. Translation files can quickly turn to disarray.
|
8
8
|
|
9
|
-
i18n-tasks improves this by
|
9
|
+
i18n-tasks improves this by i18n-tasks analysing code statically, without running it. It scans calls such as `I18n.t('some.key')` and provides reports on key usage, missing, and unused keys.
|
10
10
|
It can also pre-fill missing keys, including from Google Translate, and it can remove unused keys as well.
|
11
11
|
|
12
12
|
i18n-tasks can be used with any project using [i18n][i18n-gem] (default in Rails), or similar, even if it isn't ruby.
|
@@ -18,11 +18,9 @@ i18n-tasks can be used with any project using [i18n][i18n-gem] (default in Rails
|
|
18
18
|
Add to Gemfile:
|
19
19
|
|
20
20
|
```ruby
|
21
|
-
gem 'i18n-tasks', '~> 0.
|
21
|
+
gem 'i18n-tasks', '~> 0.5.0'
|
22
22
|
```
|
23
23
|
|
24
|
-
i18n-tasks does not load or execute any of the application's code but performs static-only analysic.
|
25
|
-
This means you can install the gem and run it on a project without adding it to Gemfile.
|
26
24
|
|
27
25
|
## Usage
|
28
26
|
|
@@ -311,6 +309,10 @@ translation:
|
|
311
309
|
api_key: <Google Translate API key>
|
312
310
|
```
|
313
311
|
|
312
|
+
## Interactive Console
|
313
|
+
|
314
|
+
`i18n-tasks irb` starts an IRB session in i18n-tasks context. Type `guide` for more information.
|
315
|
+
|
314
316
|
## RSpec integration
|
315
317
|
|
316
318
|
You might want to test for missing and unused translations as part of your test suite.
|
@@ -335,8 +337,7 @@ describe 'I18n' do
|
|
335
337
|
end
|
336
338
|
end
|
337
339
|
```
|
338
|
-
|
339
|
-
## XLSX
|
340
|
+
### XLSX
|
340
341
|
|
341
342
|
Export missing and unused data to XLSX:
|
342
343
|
|
@@ -344,14 +345,9 @@ Export missing and unused data to XLSX:
|
|
344
345
|
i18n-tasks xlsx-report
|
345
346
|
```
|
346
347
|
|
348
|
+
### HTML
|
347
349
|
|
348
|
-
|
349
|
-
|
350
|
-
While i18n-tasks does not provide an HTML version of the report, you can add [one like this](https://gist.github.com/glebm/6887030).
|
351
|
-
|
352
|
-
---
|
353
|
-
|
354
|
-
This was originally developed for [Zuigo](http://zuigo.com/), a platform to organize and discover events.
|
350
|
+
While i18n-tasks does not provide an HTML version of the report, you can add [one like this](https://gist.github.com/glebm/bdd3ab6d12d915f0c81b).
|
355
351
|
|
356
352
|
[MIT license]: /LICENSE.txt
|
357
353
|
[travis]: https://travis-ci.org/glebm/i18n-tasks
|
data/i18n-tasks.gemspec
CHANGED
@@ -10,7 +10,7 @@ Gem::Specification.new do |s|
|
|
10
10
|
s.email = ['glex.spb@gmail.com']
|
11
11
|
s.summary = %q{Manage translations in ruby applications with the awesome power of static analysis — Edit}
|
12
12
|
s.description = %q{
|
13
|
-
i18n-tasks
|
13
|
+
i18n-tasks helps you find and manage missing and unused translations.
|
14
14
|
|
15
15
|
It scans calls such as `I18n.t('some.key')` and provides reports on key usage, missing, and unused keys.
|
16
16
|
It can also can pre-fill missing keys, including from Google Translate, and it can remove unused keys as well.
|
data/lib/i18n/tasks.rb
CHANGED
data/lib/i18n/tasks/base_task.rb
CHANGED
@@ -9,7 +9,6 @@ require 'i18n/tasks/missing_keys'
|
|
9
9
|
require 'i18n/tasks/unused_keys'
|
10
10
|
require 'i18n/tasks/google_translation'
|
11
11
|
require 'i18n/tasks/fill_tasks'
|
12
|
-
require 'i18n/tasks/file_structure'
|
13
12
|
require 'i18n/tasks/data'
|
14
13
|
require 'i18n/tasks/configuration'
|
15
14
|
|
@@ -26,12 +25,15 @@ module I18n
|
|
26
25
|
include GoogleTranslation
|
27
26
|
include Logging
|
28
27
|
include Configuration
|
29
|
-
include FileStructure
|
30
28
|
include Data
|
31
29
|
|
32
30
|
def initialize(config = {})
|
33
31
|
self.config = config || {}
|
34
32
|
end
|
33
|
+
|
34
|
+
def inspect
|
35
|
+
"i18n-tasks BaseTask config: #{config_for_inspect}"
|
36
|
+
end
|
35
37
|
end
|
36
38
|
end
|
37
39
|
end
|
data/lib/i18n/tasks/commands.rb
CHANGED
@@ -20,8 +20,12 @@ module I18n::Tasks
|
|
20
20
|
end
|
21
21
|
|
22
22
|
desc 'show unused translations'
|
23
|
-
|
24
|
-
|
23
|
+
opts do
|
24
|
+
on '-l', :locales=, 'Filter by locale (default: all)', on_locale_opt
|
25
|
+
end
|
26
|
+
cmd :unused do |opt = {}|
|
27
|
+
parse_locales! opt
|
28
|
+
terminal_report.unused_keys i18n_task.unused_keys(opt)
|
25
29
|
end
|
26
30
|
|
27
31
|
desc 'translate missing keys with Google Translate'
|
@@ -64,7 +68,7 @@ module I18n::Tasks
|
|
64
68
|
end
|
65
69
|
cmd :find do |opt = {}|
|
66
70
|
opt[:filter] ||= opt.delete(:pattern) || opt[:arguments].try(:first)
|
67
|
-
terminal_report.used_keys i18n_task.
|
71
|
+
terminal_report.used_keys i18n_task.used_tree(key_filter: opt[:filter].presence, source_locations: true)
|
68
72
|
end
|
69
73
|
|
70
74
|
desc 'normalize translation data: sort and move to the right files'
|
@@ -81,15 +85,15 @@ module I18n::Tasks
|
|
81
85
|
on '-l', :locales=, 'Locales to remove unused keys from (default: all)', on_locale_opt
|
82
86
|
end
|
83
87
|
cmd :remove_unused do |opt = {}|
|
84
|
-
parse_locales!
|
85
|
-
unused_keys = i18n_task.unused_keys
|
88
|
+
parse_locales! opt
|
89
|
+
unused_keys = i18n_task.unused_keys(opt)
|
86
90
|
if unused_keys.present?
|
87
91
|
terminal_report.unused_keys(unused_keys)
|
88
92
|
unless ENV['CONFIRM']
|
89
|
-
exit 1 unless agree(red "
|
93
|
+
exit 1 unless agree(red "#{unused_keys.leaves.count} translations will be removed in #{bold opt[:locales] * ', '}#{red '.'} " + yellow('Continue? (yes/no)') + ' ')
|
90
94
|
end
|
91
95
|
i18n_task.remove_unused!(opt[:locales])
|
92
|
-
$stderr.puts "Removed #{unused_keys.
|
96
|
+
$stderr.puts "Removed #{unused_keys.leaves.count} keys"
|
93
97
|
else
|
94
98
|
$stderr.puts bold green 'No unused keys to remove'
|
95
99
|
end
|
@@ -100,7 +104,6 @@ module I18n::Tasks
|
|
100
104
|
cfg = i18n_task.config_for_inspect.to_yaml
|
101
105
|
cfg.sub! /\A---\n/, ''
|
102
106
|
cfg.gsub! /^([^\s-].+?:)/, Term::ANSIColor.cyan(Term::ANSIColor.bold('\1'))
|
103
|
-
cfg.gsub! '!ruby/hash:ActiveSupport::HashWithIndifferentAccess', ''
|
104
107
|
puts cfg
|
105
108
|
end
|
106
109
|
|
@@ -119,13 +122,10 @@ module I18n::Tasks
|
|
119
122
|
spreadsheet_report.save_report opt[:path]
|
120
123
|
end
|
121
124
|
|
122
|
-
desc '
|
125
|
+
desc 'REPL session within i18n-tasks context'
|
123
126
|
cmd :irb do
|
124
|
-
require '
|
125
|
-
|
126
|
-
IRB.conf[:MAIN_CONTEXT] = IRB::Irb.new.context
|
127
|
-
require 'irb/ext/multi-irb'
|
128
|
-
IRB.irb nil, i18n_task
|
127
|
+
require 'i18n/tasks/console_context'
|
128
|
+
::I18n::Tasks::ConsoleContext.start
|
129
129
|
end
|
130
130
|
|
131
131
|
protected
|
@@ -85,6 +85,11 @@ module I18n::Tasks::Configuration
|
|
85
85
|
@config_sections[:base_locale] ||= (config[:base_locale] || 'en').to_s
|
86
86
|
end
|
87
87
|
|
88
|
+
def ignore_config(type = nil)
|
89
|
+
key = type ? "ignore_#{type}" : 'ignore'
|
90
|
+
@config_sections[key] ||= config[key]
|
91
|
+
end
|
92
|
+
|
88
93
|
# evaluated configuration (as the app sees it)
|
89
94
|
def config_sections
|
90
95
|
# init all sections
|
@@ -94,13 +99,16 @@ module I18n::Tasks::Configuration
|
|
94
99
|
search_config
|
95
100
|
relative_roots
|
96
101
|
translation_config
|
102
|
+
[nil, :missing, :unused, :eq_base].each do |ignore_type|
|
103
|
+
ignore_config ignore_type
|
104
|
+
end
|
97
105
|
@config_sections
|
98
106
|
end
|
99
107
|
|
100
108
|
def config_for_inspect
|
101
109
|
# hide empty sections, stringify keys
|
102
|
-
Hash[config_sections.reject { |k, v| v.empty? }.map { |k, v|
|
103
|
-
[k.to_s, v.respond_to?(:
|
110
|
+
Hash[config_sections.reject { |k, v| v.nil? || v.empty? }.map { |k, v|
|
111
|
+
[k.to_s, v.respond_to?(:deep_stringify_keys) ? v.deep_stringify_keys : v] }].tap do |h|
|
104
112
|
h.each do |_k, v|
|
105
113
|
if v.is_a?(Hash) && v.key?('config')
|
106
114
|
v.merge! v.delete('config')
|
@@ -0,0 +1,73 @@
|
|
1
|
+
module I18n::Tasks
|
2
|
+
class ConsoleContext < BaseTask
|
3
|
+
def banner
|
4
|
+
puts Messages.banner
|
5
|
+
end
|
6
|
+
|
7
|
+
def guide
|
8
|
+
puts Messages.guide
|
9
|
+
end
|
10
|
+
|
11
|
+
class << self
|
12
|
+
def start
|
13
|
+
require 'irb'
|
14
|
+
IRB.setup nil
|
15
|
+
ctx = IRB::Irb.new.context
|
16
|
+
IRB.conf[:MAIN_CONTEXT] = ctx
|
17
|
+
STDERR.puts Messages.banner
|
18
|
+
require 'irb/ext/multi-irb'
|
19
|
+
IRB.irb nil, new
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
module Messages
|
24
|
+
include Term::ANSIColor
|
25
|
+
extend self
|
26
|
+
|
27
|
+
def banner
|
28
|
+
bold("i18n-tasks v#{I18n::Tasks::VERSION} IRB") + "\nType #{green 'guide'} to learn more"
|
29
|
+
end
|
30
|
+
|
31
|
+
def guide
|
32
|
+
green(bold "i18n-tasks IRB Quick Start guide") + "\n" + <<-TEXT
|
33
|
+
#{yellow 'Data as trees'}
|
34
|
+
data[base_locale]
|
35
|
+
missing_tree(locale, compared_to = base_locale)
|
36
|
+
used_tree(source_locations: false, key_filter: nil)
|
37
|
+
unused_tree(locale)
|
38
|
+
Tree::Siblings['es' => {'hello' => 'Hola'}]
|
39
|
+
|
40
|
+
#{yellow 'Traversal'}
|
41
|
+
tree = missing_tree(base_locale)
|
42
|
+
tree.nodes { |node| }
|
43
|
+
tree.nodes.to_a
|
44
|
+
tree.leaves { |node| }
|
45
|
+
tree.each { |root_node| }
|
46
|
+
# also levels, depth_first, and breadth_first
|
47
|
+
|
48
|
+
#{yellow 'Select nodes'}
|
49
|
+
tree.select_nodes { |node| } # new tree with only selected nodes
|
50
|
+
|
51
|
+
#{yellow 'Match by full key'}
|
52
|
+
tree.select_keys { |key, leaf| } # new tree with only selected keys
|
53
|
+
tree.grep_keys(/hello/) # grep, using ===
|
54
|
+
tree.keys { |key, leaf| } # enumerate over [full_key, leaf_node]
|
55
|
+
# Pass {root: true} to include root node in full_key (usually locale)
|
56
|
+
|
57
|
+
#{yellow 'Nodes'}
|
58
|
+
node = missing_tree(base_locale).leaves.first
|
59
|
+
node.key # only the part after the last dot
|
60
|
+
node.full_key # full key. Includes root key, pass {root: false} to override.
|
61
|
+
# also: value, value_or_children_hash, data, walk_to_root, walk_from_root
|
62
|
+
Tree::Node.new(key: 'en')
|
63
|
+
|
64
|
+
#{yellow 'Keys'}
|
65
|
+
t(key, locale)
|
66
|
+
key_value?(key, locale)
|
67
|
+
depluralize_key(key, locale) # convert 'hat.one' to 'hat'
|
68
|
+
absolutize_key(key, path) # '.title' to 'users.index.title'
|
69
|
+
TEXT
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
data/lib/i18n/tasks/data.rb
CHANGED
@@ -21,12 +21,6 @@ module I18n::Tasks
|
|
21
21
|
data.t(key, locale)
|
22
22
|
end
|
23
23
|
|
24
|
-
def missing_tree(locale, compared_to = base_locale)
|
25
|
-
data[compared_to].select_keys(root: false) { |key, node|
|
26
|
-
!key_value?(key, locale) && !ignore_key?(key, :missing)
|
27
|
-
}
|
28
|
-
end
|
29
|
-
|
30
24
|
def tree(locale)
|
31
25
|
data[locale][locale].children
|
32
26
|
end
|
@@ -57,46 +51,5 @@ module I18n::Tasks
|
|
57
51
|
data[target_locale] = data[target_locale]
|
58
52
|
end
|
59
53
|
end
|
60
|
-
|
61
|
-
# if :locales option present, call update_locale_data for each locale
|
62
|
-
# otherwise, call update_locale_data for :locale option or base locale
|
63
|
-
# @option opts [Array] :locales
|
64
|
-
# @option opts [String] :locale
|
65
|
-
def update_data(opts = {})
|
66
|
-
if opts.key?(:locales)
|
67
|
-
locales = (Array(opts[:locales]).presence || self.locales).map(&:to_s)
|
68
|
-
# make sure base_locale always comes first if present
|
69
|
-
locales = [base_locale] + (locales - [base_locale]) if locales.include?(base_locale)
|
70
|
-
opts = opts.except(:locales)
|
71
|
-
locales.each { |locale| update_locale_data(locale, opts.merge(locale: locale)) }
|
72
|
-
else
|
73
|
-
update_locale_data(opts[:locale] || base_locale, opts)
|
74
|
-
end
|
75
|
-
end
|
76
|
-
|
77
|
-
# @param locale
|
78
|
-
# @option opts [Array|Proc] :keys keys to update, if proc call with locale
|
79
|
-
# @option opts [String|Proc] value, if proc call with each key
|
80
|
-
# @option opts [String|Proc] values, if proc call with all the keys
|
81
|
-
def update_locale_data(locale, opts = {})
|
82
|
-
locale = locale.to_s
|
83
|
-
keys = opts[:keys]
|
84
|
-
keys = keys.call(locale) if keys.respond_to?(:call)
|
85
|
-
return if keys.empty?
|
86
|
-
|
87
|
-
values = opts[:values]
|
88
|
-
values = values.call(keys, locale) if values.respond_to?(:call)
|
89
|
-
values ||= begin
|
90
|
-
value = opts[:value] or raise 'pass value or values'
|
91
|
-
if value.respond_to?(:call)
|
92
|
-
keys.map { |key| value.call(key, locale) }
|
93
|
-
else
|
94
|
-
[value] * keys.size
|
95
|
-
end
|
96
|
-
end
|
97
|
-
data[locale] = tree(locale).merge!(
|
98
|
-
Tree::Siblings.from_flat_pairs keys.map(&:to_s).zip(values)
|
99
|
-
).parent
|
100
|
-
end
|
101
54
|
end
|
102
55
|
end
|
@@ -8,7 +8,12 @@ module I18n::Tasks
|
|
8
8
|
|
9
9
|
# @return [Hash] locale tree
|
10
10
|
def parse(str, options)
|
11
|
-
YAML.load
|
11
|
+
if YAML.method(:load).arity.abs == 2
|
12
|
+
YAML.load(str, options || {})
|
13
|
+
else
|
14
|
+
# older jruby and rbx 2.2.7 do not accept options
|
15
|
+
YAML.load(str)
|
16
|
+
end
|
12
17
|
end
|
13
18
|
|
14
19
|
# @return [String]
|
@@ -15,14 +15,14 @@ module I18n::Tasks
|
|
15
15
|
return to_enum(:route, locale, forest) unless block
|
16
16
|
out = {}
|
17
17
|
not_found = Set.new
|
18
|
-
forest.keys
|
18
|
+
forest.keys do |key, node|
|
19
19
|
locale_key = "#{locale}.#{key}"
|
20
|
-
path = adapter[locale][locale_key].data[:path
|
20
|
+
path = adapter[locale][locale_key].try(:data).try(:[], :path)
|
21
21
|
|
22
22
|
# infer from base
|
23
23
|
unless path
|
24
24
|
path = base_tree["#{base_locale}.#{key}"].try(:data).try(:[], :path)
|
25
|
-
path = path.try :sub, /(
|
25
|
+
path = path.try :sub, /(^|[\/.])#{base_locale}(?=\.)/, "\\1#{locale}"
|
26
26
|
end
|
27
27
|
|
28
28
|
if path
|
@@ -32,10 +32,10 @@ module I18n::Tasks
|
|
32
32
|
end
|
33
33
|
end
|
34
34
|
out.each do |dest, keys|
|
35
|
-
block.yield dest, forest.select_keys { |key, _| keys.include?(key) }
|
35
|
+
block.yield dest, forest.select_keys(root: true) { |key, _| keys.include?(key) }
|
36
36
|
end
|
37
37
|
if not_found.present?
|
38
|
-
super(locale, forest.select_keys { |key, _| not_found.include?(key) }, &block)
|
38
|
+
super(locale, forest.select_keys(root: true) { |key, _| not_found.include?(key) }, &block)
|
39
39
|
end
|
40
40
|
end
|
41
41
|
|
@@ -28,7 +28,7 @@ module I18n::Tasks
|
|
28
28
|
return to_enum(:route, locale, forest) unless block
|
29
29
|
locale = locale.to_s
|
30
30
|
out = {}
|
31
|
-
forest.keys
|
31
|
+
forest.keys do |key, _node|
|
32
32
|
pattern, path = routes.detect { |route| route[0] =~ key }
|
33
33
|
if pattern
|
34
34
|
key_match = $~
|
@@ -41,7 +41,7 @@ module I18n::Tasks
|
|
41
41
|
end
|
42
42
|
out.each do |dest, keys|
|
43
43
|
block.yield dest,
|
44
|
-
forest.select_keys { |key, _| keys.include?(key) }
|
44
|
+
forest.select_keys(root: true) { |key, _| keys.include?(key) }
|
45
45
|
end
|
46
46
|
end
|
47
47
|
|