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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: f361cd15dc7c4dbe60aedba3245481fb4180b91e
4
- data.tar.gz: 5c1fa95a09f2cf826c12eb913145f91e2906758a
3
+ metadata.gz: b615d3bad6d043fd662de2ba05caba8230ab6d5a
4
+ data.tar.gz: 0e16ff6699e70fa0ac00aecddfe674d9e56d4352
5
5
  SHA512:
6
- metadata.gz: db244b5be78460bac7f310996b389fd71296a788509700d07ae86b2f4ac68e920b70dc1293febebba07e5b7871a4c4b42c3856ec042fdbd9e64d2a13a1e71624
7
- data.tar.gz: 24a7dff3fb48aa9d4b8371143669a491d079bbffc1630a97964607627293b7ae70a9576d4dda4241dbf9b11f8eea44a6f1c7b697cb3c9910f28a77a9dbd54301
6
+ metadata.gz: 613137fe779a345d90bafec320ed973e16823e460b2a772bf947ac4e885aae87391e311133842b9d78de69945942214a67bc92151b3e66c6e02fb622bd01e7fc
7
+ data.tar.gz: 9f7b694d7c4c012d19935089311b87809fbe6279e0184bd1d4b8619e10336de5afca4539c111dbd610bb8786965d890b2eaf4374bc985afb8572702f81ea1fc3
@@ -3,8 +3,8 @@ rvm:
3
3
  - 2.1.1
4
4
  - 2.0.0
5
5
  - 1.9.3
6
- - jruby-head
7
- - rbx-2
6
+ - jruby-1.7.13
7
+ - rbx-2.2.10
8
8
  env:
9
9
  global:
10
10
  - TRAVIS=1
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.2'
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
- ```bash
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
- ```bash
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
- ```bash
335
- i18n-tasks xlsx-report
360
+ ```console
361
+ $ i18n-tasks xlsx-report
336
362
  ```
337
363
 
338
364
  ### HTML
@@ -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 (e.g. en,fr) or all (default: all), also accepted as arguments without -l',
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].end_with?('_html'.freeze)
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
- translated = EasyTranslate.translate(list.map { |l| l[1].gsub(INTERPOLATION_KEY_RE, UNTRANSLATABLE_STRING) }, opts)
34
- if translated.blank?
35
- raise CommandError.new('Google Translate returned no results. Make sure billing information is set at https://code.google.com/apis/console.')
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
- !key_expression?(key) && locale_key_missing?(locale, key)
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, *args)
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
- def valid_key?(key)
121
- key =~ VALID_KEY_RE && !(@key_filter && @key_filter_pattern !~ key)
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, text = read_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
- def unused_tree(locale = base_locale)
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
@@ -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
- @used_key_names ||= used_tree.key_names
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:
@@ -1,6 +1,6 @@
1
1
  # coding: utf-8
2
2
  module I18n
3
3
  module Tasks
4
- VERSION = '0.6.2'
4
+ VERSION = '0.6.3'
5
5
  end
6
6
  end
@@ -6,8 +6,9 @@ describe 'Google Translation' do
6
6
  include I18n::Tasks::GoogleTranslation
7
7
 
8
8
  tests = [
9
- text_test = ['a', "Hello - %{user} O'neill!", "Hola - %{user} O'neill!"],
10
- html_test = ['a_html', "Hello - <b>%{user} O'neill</b>", "Hola - <b>%{user} O'neill</b>"]
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 'works' do
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'])
@@ -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.2
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-22 00:00:00.000000000 Z
11
+ date: 2014-07-24 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: erubis