i18n-tasks 0.6.2 → 0.6.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.travis.yml +2 -2
- data/CHANGES.md +5 -0
- data/README.md +33 -7
- data/lib/i18n/tasks/commands.rb +6 -1
- data/lib/i18n/tasks/commands_base.rb +4 -0
- data/lib/i18n/tasks/google_translation.rb +48 -15
- data/lib/i18n/tasks/missing_keys.rb +2 -2
- data/lib/i18n/tasks/scanners/base_scanner.rb +12 -6
- data/lib/i18n/tasks/scanners/pattern_scanner.rb +4 -2
- data/lib/i18n/tasks/unused_keys.rb +5 -3
- data/lib/i18n/tasks/used_keys.rb +10 -5
- data/lib/i18n/tasks/version.rb +1 -1
- data/spec/google_translate_spec.rb +4 -3
- data/spec/i18n_tasks_spec.rb +7 -0
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b615d3bad6d043fd662de2ba05caba8230ab6d5a
|
4
|
+
data.tar.gz: 0e16ff6699e70fa0ac00aecddfe674d9e56d4352
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 613137fe779a345d90bafec320ed973e16823e460b2a772bf947ac4e885aae87391e311133842b9d78de69945942214a67bc92151b3e66c6e02fb622bd01e7fc
|
7
|
+
data.tar.gz: 9f7b694d7c4c012d19935089311b87809fbe6279e0184bd1d4b8619e10336de5afca4539c111dbd610bb8786965d890b2eaf4374bc985afb8572702f81ea1fc3
|
data/.travis.yml
CHANGED
data/CHANGES.md
CHANGED
@@ -1,3 +1,8 @@
|
|
1
|
+
## 0.6.3
|
2
|
+
|
3
|
+
* Strict mode added for `unused` and `remove-unused`. When passed `-s` or `--strict`, these tasks will not attempt to infer dynamic key usages, such as `t("category.#{category.key}")`.
|
4
|
+
* Arrays are now supported as values for Google Translate [#77](https://github.com/glebm/i18n-tasks/issues/77)
|
5
|
+
|
1
6
|
## 0.6.2
|
2
7
|
|
3
8
|
* New task to show locale data: `i18n-tasks data`
|
data/README.md
CHANGED
@@ -18,9 +18,20 @@ 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.6.
|
21
|
+
gem 'i18n-tasks', '~> 0.6.3'
|
22
22
|
```
|
23
23
|
|
24
|
+
Copy default [config file](#configuration) (optional):
|
25
|
+
|
26
|
+
```console
|
27
|
+
$ cp $(i18n-tasks gem-path)/templates/config/i18n-tasks.yml config/
|
28
|
+
```
|
29
|
+
|
30
|
+
Copy [rspec test](#rspec-integration) (optional):
|
31
|
+
|
32
|
+
```console
|
33
|
+
$ cp $(i18n-tasks gem-path)/templates/rspec/i18n_spec.rb spec/
|
34
|
+
```
|
24
35
|
|
25
36
|
## Usage
|
26
37
|
|
@@ -38,6 +49,7 @@ Available commands:
|
|
38
49
|
unused show unused translations
|
39
50
|
eq-base show translations equal to base value
|
40
51
|
find show where the keys are used in the code
|
52
|
+
data show locale data
|
41
53
|
translate-missing translate missing keys with Google Translate
|
42
54
|
add-missing add missing keys to the locales
|
43
55
|
normalize normalize translation data: sort and move to the right files
|
@@ -50,6 +62,17 @@ Available commands:
|
|
50
62
|
See `<command> --help` for more information on a specific command.
|
51
63
|
```
|
52
64
|
|
65
|
+
Show tasks accept a format option, and all but `find` accept locales as arguments, e.g:
|
66
|
+
|
67
|
+
```bash
|
68
|
+
$ i18n-tasks data --help
|
69
|
+
Usage: i18n-tasks data [options]
|
70
|
+
-l, --locales Filter by locale(s), comma-separated list (en,fr) or all (default), or pass arguments without -l
|
71
|
+
-f, --format Output format: terminal-table, yaml, json, keys, inspect. Default: terminal-table.
|
72
|
+
-h, --help Display this help message.
|
73
|
+
$ i18n-tasks data -fkeys en es fr
|
74
|
+
```
|
75
|
+
|
53
76
|
#### Add missing keys
|
54
77
|
|
55
78
|
You can add missing values, generated from the key (for base locale) or copied from the base locale (for other locales).
|
@@ -98,6 +121,9 @@ i18n-tasks unused
|
|
98
121
|
i18n-tasks remove-unused
|
99
122
|
```
|
100
123
|
|
124
|
+
These tasks will infer dynamic key usage such as `t("category.\#{category.name}")` by default.
|
125
|
+
Pass `-s` or `--strict` to disable this feature.
|
126
|
+
|
101
127
|
#### Features
|
102
128
|
|
103
129
|
Relative keys (`t '.title'`) and plural keys (`key.{one,many,other,...}`) are fully supported.
|
@@ -126,8 +152,8 @@ Inspect configuration with `i18n-tasks config`.
|
|
126
152
|
|
127
153
|
Install the default config file with:
|
128
154
|
|
129
|
-
```
|
130
|
-
cp $(i18n-tasks gem-path)/templates/config/i18n-tasks.yml config/
|
155
|
+
```console
|
156
|
+
$ cp $(i18n-tasks gem-path)/templates/config/i18n-tasks.yml config/
|
131
157
|
```
|
132
158
|
|
133
159
|
### Locales
|
@@ -323,16 +349,16 @@ translation:
|
|
323
349
|
You might want to test for missing and unused translations as part of your test suite.
|
324
350
|
Install the spec file:
|
325
351
|
|
326
|
-
```
|
327
|
-
cp $(i18n-tasks gem-path)/templates/rspec/i18n_spec.rb spec/
|
352
|
+
```console
|
353
|
+
$ cp $(i18n-tasks gem-path)/templates/rspec/i18n_spec.rb spec/
|
328
354
|
```
|
329
355
|
|
330
356
|
### XLSX
|
331
357
|
|
332
358
|
Export missing and unused data to XLSX:
|
333
359
|
|
334
|
-
```
|
335
|
-
i18n-tasks xlsx-report
|
360
|
+
```console
|
361
|
+
$ i18n-tasks xlsx-report
|
336
362
|
```
|
337
363
|
|
338
364
|
### HTML
|
data/lib/i18n/tasks/commands.rb
CHANGED
@@ -12,13 +12,16 @@ module I18n::Tasks
|
|
12
12
|
OPT = {
|
13
13
|
locale: proc {
|
14
14
|
on '-l', :locales=,
|
15
|
-
'Filter by locale(s), comma-separated list (
|
15
|
+
'Filter by locale(s), comma-separated list (en,fr) or all (default), or pass arguments without -l',
|
16
16
|
on_locale_opt
|
17
17
|
},
|
18
18
|
format: proc {
|
19
19
|
on '-f', :format=,
|
20
20
|
"Output format: #{VALID_TREE_FORMATS * ', '}. Default: terminal-table.",
|
21
21
|
{default: 'terminal-table', argument: true, optional: false}
|
22
|
+
},
|
23
|
+
strict: proc {
|
24
|
+
on :s, :strict, %Q(Do not infer dynamic key usage such as `t("category.\#{category.name}")`)
|
22
25
|
}
|
23
26
|
}
|
24
27
|
desc 'show missing translations'
|
@@ -36,6 +39,7 @@ module I18n::Tasks
|
|
36
39
|
opts do
|
37
40
|
instance_exec &OPT[:locale]
|
38
41
|
instance_exec &OPT[:format]
|
42
|
+
instance_exec &OPT[:strict]
|
39
43
|
end
|
40
44
|
cmd :unused do |opt = {}|
|
41
45
|
parse_locales! opt
|
@@ -118,6 +122,7 @@ module I18n::Tasks
|
|
118
122
|
desc 'remove unused keys'
|
119
123
|
opts do
|
120
124
|
on '-l', :locales=, 'Locales to remove unused keys from (comma-separated, default: all)', on_locale_opt
|
125
|
+
instance_exec &OPT[:strict]
|
121
126
|
end
|
122
127
|
cmd :remove_unused do |opt = {}|
|
123
128
|
parse_locales! opt
|
@@ -20,8 +20,12 @@ module I18n::Tasks
|
|
20
20
|
end
|
21
21
|
end
|
22
22
|
|
23
|
+
VALID_LOCALE_RE = /\A\w[\w\-_\.]*\z/i
|
23
24
|
def parse_locales!(opt)
|
24
25
|
opt[:locales] = locales_opt(opt[:arguments].presence || opt[:locales]).tap do |locales|
|
26
|
+
locales.each do |locale|
|
27
|
+
raise CommandError.new("Invalid locale: #{locale}") if VALID_LOCALE_RE !~ locale
|
28
|
+
end
|
25
29
|
log_verbose "locales for the command are #{locales.inspect}"
|
26
30
|
end
|
27
31
|
end
|
@@ -17,7 +17,7 @@ module I18n::Tasks
|
|
17
17
|
key_idx = {}
|
18
18
|
list.each_with_index { |k_v, i| key_idx[k_v[0]] = i }
|
19
19
|
list.group_by { |k_v|
|
20
|
-
k_v[0]
|
20
|
+
!!(k_v[0] =~ /[.\-_]html\z/.freeze)
|
21
21
|
}.map do |html, slice|
|
22
22
|
t_opts = opts.merge(html ? {html: true} : {format: 'text'})
|
23
23
|
fetch_google_translations slice, t_opts
|
@@ -26,26 +26,59 @@ module I18n::Tasks
|
|
26
26
|
}
|
27
27
|
end
|
28
28
|
|
29
|
-
INTERPOLATION_KEY_RE = /%\{[^}]+\}/
|
30
|
-
UNTRANSLATABLE_STRING = 'zxzxzx'
|
31
|
-
|
32
29
|
def fetch_google_translations(list, opts)
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
end
|
37
|
-
translated.each_with_index { |translation, i|
|
38
|
-
if (original = list[i][1]) =~ INTERPOLATION_KEY_RE
|
39
|
-
interpolation_keys = original.scan(INTERPOLATION_KEY_RE)
|
40
|
-
i = -1
|
41
|
-
translation.gsub!(Regexp.new(UNTRANSLATABLE_STRING, Regexp::IGNORECASE)) { interpolation_keys[i += 1] }
|
30
|
+
from_values(list, EasyTranslate.translate(to_values(list), opts)).tap do |result|
|
31
|
+
if result.blank?
|
32
|
+
raise CommandError.new('Google Translate returned no results. Make sure billing information is set at https://code.google.com/apis/console.')
|
42
33
|
end
|
43
|
-
|
44
|
-
list.map(&:first).zip(translated)
|
34
|
+
end
|
45
35
|
end
|
46
36
|
|
47
37
|
private
|
48
38
|
|
39
|
+
def to_values(list)
|
40
|
+
list.map { |l| dump_value l[1] }.flatten
|
41
|
+
end
|
42
|
+
|
43
|
+
def from_values(list, translated_values)
|
44
|
+
keys = list.map(&:first)
|
45
|
+
untranslated_values = list.map(&:last)
|
46
|
+
keys.zip parse_value(untranslated_values, translated_values.to_enum)
|
47
|
+
end
|
48
|
+
|
49
|
+
def dump_value(value)
|
50
|
+
if value.is_a?(Array)
|
51
|
+
# dump recursively
|
52
|
+
value.map { |v| dump_value v }
|
53
|
+
else
|
54
|
+
replace_interpolations value
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def parse_value(untranslated, each_translated)
|
59
|
+
if untranslated.is_a?(Array)
|
60
|
+
# implode array
|
61
|
+
untranslated.map { |from| parse_value(from, each_translated) }
|
62
|
+
else
|
63
|
+
value = each_translated.next
|
64
|
+
restore_interpolations untranslated, value
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
INTERPOLATION_KEY_RE = /%\{[^}]+\}/.freeze
|
69
|
+
UNTRANSLATABLE_STRING = 'zxzxzx'.freeze
|
70
|
+
|
71
|
+
# 'hello, %{name}' => 'hello, <round-trippable string>'
|
72
|
+
def replace_interpolations(value)
|
73
|
+
value.gsub INTERPOLATION_KEY_RE, UNTRANSLATABLE_STRING
|
74
|
+
end
|
75
|
+
|
76
|
+
def restore_interpolations(untranslated, translated)
|
77
|
+
return translated if untranslated !~ INTERPOLATION_KEY_RE
|
78
|
+
each_value = untranslated.scan(INTERPOLATION_KEY_RE).to_enum
|
79
|
+
translated.gsub(Regexp.new(UNTRANSLATABLE_STRING, Regexp::IGNORECASE)) { each_value.next }
|
80
|
+
end
|
81
|
+
|
49
82
|
def warn_missing_api_key
|
50
83
|
$stderr.puts Term::ANSIColor.red Term::ANSIColor.yellow 'Set Google API key via GOOGLE_TRANSLATE_API_KEY environmnet variable or translation.api_key in config/i18n-tasks.yml.
|
51
84
|
Get the key at https://code.google.com/apis/console.'
|
@@ -67,8 +67,8 @@ module I18n::Tasks
|
|
67
67
|
|
68
68
|
# keys used in the code missing translations in locale
|
69
69
|
def missing_used_tree(locale)
|
70
|
-
used_tree.select_keys { |key, _node|
|
71
|
-
|
70
|
+
used_tree(strict: true).select_keys { |key, _node|
|
71
|
+
locale_key_missing?(locale, key)
|
72
72
|
}.set_root_key(locale, type: :missing_used)
|
73
73
|
end
|
74
74
|
|
@@ -37,9 +37,9 @@ module I18n::Tasks::Scanners
|
|
37
37
|
end
|
38
38
|
|
39
39
|
# @return [Array<{key,data:{source_locations:[]}}]
|
40
|
-
def keys
|
40
|
+
def keys(opts = {})
|
41
41
|
keys = traverse_files { |path|
|
42
|
-
scan_file(path)
|
42
|
+
scan_file(path, opts)
|
43
43
|
}.reduce(:+) || []
|
44
44
|
keys.group_by(&:first).map { |key, key_loc|
|
45
45
|
[key, data: {source_locations: key_loc.map { |(k, attr)| attr[:data] }}]
|
@@ -53,7 +53,7 @@ module I18n::Tasks::Scanners
|
|
53
53
|
end
|
54
54
|
|
55
55
|
# @return [Array<Key>] keys found in file
|
56
|
-
def scan_file(path,
|
56
|
+
def scan_file(path, opts = {})
|
57
57
|
raise 'Unimplemented'
|
58
58
|
end
|
59
59
|
|
@@ -116,9 +116,15 @@ module I18n::Tasks::Scanners
|
|
116
116
|
end
|
117
117
|
|
118
118
|
VALID_KEY_RE = /^[-\w.\#{}]+$/
|
119
|
-
|
120
|
-
|
121
|
-
|
119
|
+
VALID_KEY_RE_STRICT = /^[-\w.]+$/
|
120
|
+
|
121
|
+
def valid_key?(key, strict = false)
|
122
|
+
return false if @key_filter && @key_filter_pattern !~ key
|
123
|
+
if strict
|
124
|
+
key =~ VALID_KEY_RE_STRICT && !key.end_with?('.')
|
125
|
+
else
|
126
|
+
key =~ VALID_KEY_RE
|
127
|
+
end
|
122
128
|
end
|
123
129
|
|
124
130
|
def relative_roots
|
@@ -7,12 +7,14 @@ module I18n::Tasks::Scanners
|
|
7
7
|
class PatternScanner < BaseScanner
|
8
8
|
# Extract i18n keys from file based on the pattern which must capture the key literal.
|
9
9
|
# @return [Array<Key>] keys found in file
|
10
|
-
def scan_file(path,
|
10
|
+
def scan_file(path, opts = {})
|
11
11
|
keys = []
|
12
|
+
strict = !!opts[:strict]
|
13
|
+
text = opts[:text] || read_file(path)
|
12
14
|
text.scan(pattern) do |match|
|
13
15
|
src_pos = Regexp.last_match.offset(0).first
|
14
16
|
key = match_to_key(match, path)
|
15
|
-
next unless valid_key?(key)
|
17
|
+
next unless valid_key?(key, strict)
|
16
18
|
location = src_location(path, text, src_pos)
|
17
19
|
unless exclude_line?(location[:line])
|
18
20
|
keys << [key, data: location]
|
@@ -6,13 +6,15 @@ module I18n
|
|
6
6
|
module UnusedKeys
|
7
7
|
def unused_keys(opts = {})
|
8
8
|
locales = Array(opts[:locales]).presence || self.locales
|
9
|
-
locales.map { |locale| unused_tree locale }.compact.reduce(:merge!)
|
9
|
+
locales.map { |locale| unused_tree locale, opts[:strict] }.compact.reduce(:merge!)
|
10
10
|
end
|
11
11
|
|
12
|
-
|
12
|
+
# @param [String] locale
|
13
|
+
# @param [Boolean] strict if true, do not match dynamic keys
|
14
|
+
def unused_tree(locale = base_locale, strict = false)
|
13
15
|
collapse_plural_nodes! data[locale].select_keys { |key, _node|
|
14
16
|
!ignore_key?(key, :unused) &&
|
15
|
-
!used_in_expr?(key) &&
|
17
|
+
(strict || !used_in_expr?(key)) &&
|
16
18
|
!used_key?(depluralize_key(key, locale))
|
17
19
|
}
|
18
20
|
end
|
data/lib/i18n/tasks/used_keys.rb
CHANGED
@@ -7,10 +7,11 @@ module I18n::Tasks
|
|
7
7
|
|
8
8
|
# find all keys in the source (relative keys are absolutized)
|
9
9
|
# @option opts [String] :key_filter
|
10
|
+
# @option opts [Boolean] :strict if true dynamic keys are excluded (e.g. `t("category.#{category.key}")`)
|
10
11
|
# @return [Array<String>]
|
11
12
|
def used_tree(opts = {})
|
12
13
|
return scanner.with_key_filter(opts[:key_filter]) { used_tree(opts.except(:key_filter)) } if opts[:key_filter]
|
13
|
-
key_attrs = scanner.keys
|
14
|
+
key_attrs = scanner.keys(opts.slice(:strict))
|
14
15
|
Data::Tree::Node.new(
|
15
16
|
key: 'used',
|
16
17
|
data: {key_filter: scanner.key_filter},
|
@@ -26,13 +27,17 @@ module I18n::Tasks
|
|
26
27
|
end
|
27
28
|
end
|
28
29
|
|
29
|
-
def used_key_names
|
30
|
-
|
30
|
+
def used_key_names(strict = false)
|
31
|
+
if strict
|
32
|
+
@used_key_names ||= used_tree(strict: true).key_names
|
33
|
+
else
|
34
|
+
@used_key_names ||= used_tree.key_names
|
35
|
+
end
|
31
36
|
end
|
32
37
|
|
33
38
|
# whether the key is used in the source
|
34
|
-
def used_key?(key)
|
35
|
-
used_key_names.include?(key)
|
39
|
+
def used_key?(key, strict = false)
|
40
|
+
used_key_names(strict).include?(key)
|
36
41
|
end
|
37
42
|
|
38
43
|
# @return whether the key is potentially used in a code expression such as:
|
data/lib/i18n/tasks/version.rb
CHANGED
@@ -6,8 +6,9 @@ describe 'Google Translation' do
|
|
6
6
|
include I18n::Tasks::GoogleTranslation
|
7
7
|
|
8
8
|
tests = [
|
9
|
-
text_test = ['
|
10
|
-
html_test = ['
|
9
|
+
text_test = ['key', "Hello - %{user} O'neill!", "Hola - %{user} O'neill!"],
|
10
|
+
html_test = ['html-key.html', "Hello - <b>%{user} O'neill</b>", "Hola - <b>%{user} O'neill</b>"],
|
11
|
+
array_test = ['array-key', ['Hello.', 'Goodbye.'], ['Hola.', 'Adiós.']]
|
11
12
|
]
|
12
13
|
|
13
14
|
|
@@ -16,7 +17,7 @@ describe 'Google Translation' do
|
|
16
17
|
delegate :i18n_cmd, :i18n_task, :in_test_app_dir, to: :TestCodebase
|
17
18
|
|
18
19
|
context 'API' do
|
19
|
-
it
|
20
|
+
it "works with #{tests.map(&:first)}" do
|
20
21
|
# Just one test with all the cases to lower the Google bill
|
21
22
|
translations = google_translate(
|
22
23
|
tests.map { |t| t[0..1] }, from: :en, to: :es, key: ENV['GOOGLE_TRANSLATE_API_KEY'])
|
data/spec/i18n_tasks_spec.rb
CHANGED
@@ -38,12 +38,19 @@ describe 'i18n-tasks' do
|
|
38
38
|
end
|
39
39
|
|
40
40
|
let(:expected_unused_keys) { %w(unused.a unused.numeric unused.plural).map { |k| %w(en es).map { |l| "#{l}.#{k}" } }.reduce(:+) }
|
41
|
+
let(:expected_unused_keys_strict) { expected_unused_keys + %w(hash.pattern.a hash.pattern2.a).map { |k| %w(en es).map { |l| "#{l}.#{k}" } }.reduce(:+) }
|
41
42
|
describe 'unused' do
|
42
43
|
it 'detects unused' do
|
43
44
|
capture_stderr do
|
44
45
|
expect(run_cmd :unused).to be_i18n_keys expected_unused_keys
|
45
46
|
end
|
46
47
|
end
|
48
|
+
|
49
|
+
it 'detects unused (--strict)' do
|
50
|
+
capture_stderr do
|
51
|
+
expect(run_cmd :unused, strict: true).to be_i18n_keys expected_unused_keys_strict
|
52
|
+
end
|
53
|
+
end
|
47
54
|
end
|
48
55
|
|
49
56
|
describe 'remove_unused' do
|
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: 0.6.
|
4
|
+
version: 0.6.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- glebm
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2014-07-
|
11
|
+
date: 2014-07-24 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: erubis
|