i18n-tasks 0.4.5 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
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