i18n-tasks 0.6.2 → 0.6.3
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/.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
|