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 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