i18n-tasks 0.4.5 → 0.5.0

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.
Files changed (45) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +0 -4
  3. data/CHANGES.md +7 -0
  4. data/README.md +10 -14
  5. data/i18n-tasks.gemspec +1 -1
  6. data/lib/i18n/tasks.rb +0 -2
  7. data/lib/i18n/tasks/base_task.rb +4 -2
  8. data/lib/i18n/tasks/commands.rb +14 -14
  9. data/lib/i18n/tasks/configuration.rb +10 -2
  10. data/lib/i18n/tasks/console_context.rb +73 -0
  11. data/lib/i18n/tasks/data.rb +0 -47
  12. data/lib/i18n/tasks/data/adapter/yaml_adapter.rb +6 -1
  13. data/lib/i18n/tasks/data/file_system_base.rb +1 -1
  14. data/lib/i18n/tasks/data/router/conservative_router.rb +5 -5
  15. data/lib/i18n/tasks/data/router/pattern_router.rb +2 -2
  16. data/lib/i18n/tasks/data/tree/node.rb +47 -36
  17. data/lib/i18n/tasks/data/tree/nodes.rb +0 -4
  18. data/lib/i18n/tasks/data/tree/siblings.rb +54 -9
  19. data/lib/i18n/tasks/data/tree/traversal.rb +62 -23
  20. data/lib/i18n/tasks/fill_tasks.rb +29 -21
  21. data/lib/i18n/tasks/ignore_keys.rb +1 -1
  22. data/lib/i18n/tasks/key_pattern_matching.rb +17 -0
  23. data/lib/i18n/tasks/missing_keys.rb +39 -44
  24. data/lib/i18n/tasks/plural_keys.rb +14 -1
  25. data/lib/i18n/tasks/reports/base.rb +28 -8
  26. data/lib/i18n/tasks/reports/spreadsheet.rb +9 -8
  27. data/lib/i18n/tasks/reports/terminal.rb +33 -29
  28. data/lib/i18n/tasks/scanners/base_scanner.rb +22 -14
  29. data/lib/i18n/tasks/scanners/pattern_scanner.rb +2 -1
  30. data/lib/i18n/tasks/unused_keys.rb +13 -13
  31. data/lib/i18n/tasks/used_keys.rb +39 -38
  32. data/lib/i18n/tasks/version.rb +1 -1
  33. data/spec/i18n_tasks_spec.rb +41 -40
  34. data/spec/locale_tree/siblings_spec.rb +26 -1
  35. data/spec/support/i18n_tasks_output_matcher.rb +4 -1
  36. data/spec/support/trees.rb +6 -1
  37. data/spec/used_keys_spec.rb +23 -15
  38. metadata +4 -11
  39. data/lib/i18n/tasks/file_structure.rb +0 -19
  40. data/lib/i18n/tasks/key.rb +0 -48
  41. data/lib/i18n/tasks/key/key_group.rb +0 -45
  42. data/lib/i18n/tasks/key/match_pattern.rb +0 -24
  43. data/lib/i18n/tasks/key/usages.rb +0 -12
  44. data/lib/i18n/tasks/key_group.rb +0 -68
  45. data/spec/key_group_spec.rb +0 -49
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 7b205200e5f46e458947cd7efa0a3f4781555bed
4
- data.tar.gz: 3c233c02ca02b64ce1e5b02d2e068bbee0b1db2a
3
+ metadata.gz: f4ae19f0f97a5f8ae9221b45ef316b08a5efbe62
4
+ data.tar.gz: 2b957a1c692318da437ca4235aeb2a1b9e47f2b6
5
5
  SHA512:
6
- metadata.gz: 774d211a16131bcfbf7df07fdb9a0fd7d4926d227df2bb499772af12e12b77f1911dc0b9cf6e39d4f232ed41feb3bbf86781e9ca6d364b7b4d70894690f0b864
7
- data.tar.gz: bc56dfc3f345175e329904430678ddbafefcb85a73c068055cd31062dec2b8bd7ab0d6ddb924af410222bdb17b709dd6821ecbadff7b0a9668c9f20a9da9b497
6
+ metadata.gz: d00e68fb5727c9d95df84b5de56bd57d53a6adc6e9e00e1ce05c16e6f99055b30b0bc9c478dfabba238962a5d75cc75dc75b97ae4724bde1b07e4f07150b396d
7
+ data.tar.gz: 5bc8932e731220f37abce28f4b637c60ab41ed02b8e3c8ab5f0c273385ae269fae4a996e408a006d9ba59bd95bab1e1599d2cc48670ff828421fe96d4d1380d4
data/.travis.yml CHANGED
@@ -5,10 +5,6 @@ rvm:
5
5
  - 1.9.3
6
6
  - jruby-head
7
7
  - rbx-2
8
- matrix:
9
- allow_failures:
10
- # no keyword argument support in rbx
11
- - rvm: rbx-2
12
8
  env:
13
9
  global:
14
10
  - TRAVIS=1
data/CHANGES.md CHANGED
@@ -1,3 +1,10 @@
1
+ ## 0.5.0
2
+
3
+ * internals refactored to use trees everywhere
4
+ * type `guide` in `i18n-tasks irb` to learn more about the commands
5
+ * (remove-)unused tasks now work per locale
6
+ * Rubinius 2.2.7 compatibility
7
+
1
8
  ## 0.4.5
2
9
 
3
10
  * Respect tty color setting
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 finds and manages missing and unused translations in your application.
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 using static analysis. It scans calls such as `I18n.t('some.key')` and provides reports on key usage, missing, and unused keys.
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.4.5'
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
- ## HTML
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 finds and manages missing and unused translations in your application.
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
@@ -17,7 +17,5 @@ require 'term/ansicolor'
17
17
  require 'erubis'
18
18
 
19
19
  require 'i18n/tasks/version'
20
- require 'i18n/tasks/key'
21
- require 'i18n/tasks/key_group'
22
20
  require 'i18n/tasks/base_task'
23
21
 
@@ -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
@@ -20,8 +20,12 @@ module I18n::Tasks
20
20
  end
21
21
 
22
22
  desc 'show unused translations'
23
- cmd :unused do
24
- terminal_report.unused_keys
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.used_keys(key_filter: opt[:filter].presence, src_locations: true)
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!(opt)
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 "All these translations will be removed in #{bold opt[:locales] * ', '}#{red '.'} " + yellow('Continue? (yes/no)') + ' ')
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.size} 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 'irb session within i18n-tasks context'
125
+ desc 'REPL session within i18n-tasks context'
123
126
  cmd :irb do
124
- require 'irb'
125
- IRB.setup nil
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?(:stringify_keys) ? v.stringify_keys : v] }].tap do |h|
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
@@ -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(str, options || {})
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]
@@ -74,7 +74,7 @@ module I18n::Tasks
74
74
  def t(key, locale)
75
75
  tree = self[locale.to_s]
76
76
  return unless tree
77
- tree[locale][key].try(:hash_or_value)
77
+ tree[locale][key].try(:value_or_children_hash)
78
78
  end
79
79
 
80
80
  def config=(config)
@@ -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(root: false) do |key, node|
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, /(?<=[\/.])#{base_locale}(?=\.)/, locale
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(root: false) do |key, _node|
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