i18n-tasks 0.3.6 → 0.3.7

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: b81678a0e44f5c7593956fbe37f122aff4cdb7fc
4
- data.tar.gz: e5bbc6aa629d379ab04551f00d706e714b2b845c
3
+ metadata.gz: f92285f463714c33d1c83515dcd2aaf6767e4663
4
+ data.tar.gz: b82019313500a6dc1a2122b56d0bc118086071a3
5
5
  SHA512:
6
- metadata.gz: c5151ef672fedd86b317900c2621b03a09748e3275efd39221626f101dd895a53336c76ef73e2e973287c40af6d38130323cc89fa178fe0e2bce92fc3d705120
7
- data.tar.gz: ec27bed50ae7691827b57619437ce3e3b943a0b01b658e7d350a12ef0af5fa20b921209534ec766e46a8e9864f90fbc67f909c3f15d097263bd0a35845378997
6
+ metadata.gz: 1cd49cb9ec3101e8ef38c75f5cace26f4dcf2afc613a89b15ea9741ec0ab420203d04039aefe79a5996cdef5736f2eb74b2503e076e2fd29cd7e05c6fda5258a
7
+ data.tar.gz: 4fec3b2eb4ad06d80429b653ede00d06522f4710ebdcdb21341d5723d2b696812086caeab0415abc8965878fd0d1d058f3346da293725b535c2a7900da36d9ec
data/CHANGES.md CHANGED
@@ -1,3 +1,8 @@
1
+ ## 0.3.7
2
+
3
+ * Catch Errno::EPIPE to allow `i18n-tasks <command> | head` for large reports
4
+ * Improved i18n-tasks config output
5
+
1
6
  ## v0.3.6
2
7
 
3
8
  * fix issue with Google Translate
data/README.md CHANGED
@@ -2,13 +2,14 @@
2
2
 
3
3
  i18n-tasks finds and manages missing and unused translations in your application.
4
4
 
5
- The basic approach to i18n key management in frameworks such as Rails is far from perfect.
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
- in the resource files, introducing unnecessary overhead on the translators. Translation files can quickly turn to disarray.
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 uses this information to
10
- provide reports on key usage, missing and unused keys, and can prefill missing keys, including from Google Translate,
11
- and can also remove unused keys.
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.
10
+ It can also can pre-fill missing keys, including from Google Translate, and it can remove unused keys as well.
11
+
12
+ i18n-tasks can be used with any project using [i18n][i18n-gem] (default in Rails), or similar, even if it isn't ruby.
12
13
 
13
14
  <img width="534" height="288" src="https://raw.github.com/glebm/i18n-tasks/master/doc/img/i18n-tasks.png">
14
15
 
@@ -16,9 +17,12 @@ and can also remove unused keys.
16
17
 
17
18
  Add to Gemfile:
18
19
 
19
- ```ruby
20
- gem 'i18n-tasks', '~> 0.3.5'
21
- ```
20
+ ```ruby
21
+ gem 'i18n-tasks', '~> 0.3.7'
22
+ ```
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.
22
26
 
23
27
  ## Usage
24
28
 
@@ -295,4 +299,5 @@ This was originally developed for [Zuigo](http://zuigo.com/), a platform to orga
295
299
  [badge-code-climate]: https://codeclimate.com/github/glebm/i18n-tasks.png
296
300
  [badge-flattr]: https://api.flattr.com/button/flattr-badge-large.png
297
301
  [flattr]: https://flattr.com/submit/auto?user_id=glebm&url=https%3A%2F%2Fgithub.com%2Fglebm%2Fi18n-tasks
302
+ [i18n-gem]: https://github.com/svenfuchs/i18n "svenfuchs/i18n on Github"
298
303
  [screenshot-find]: https://raw.github.com/glebm/i18n-tasks/master/doc/img/i18n-usages.png "i18n-tasks find output screenshot"
data/bin/i18n-tasks CHANGED
@@ -24,16 +24,20 @@ end
24
24
  if command
25
25
  cmd = ::I18n::Tasks::Commands.new
26
26
  meth = command[0]
27
- opts = command[1].to_hash.reject{ |k, v| v.nil? }
27
+ opts = command[1].to_hash.reject { |k, v| v.nil? }
28
28
  args = command[2]
29
-
30
- if opts.empty? && args.empty?
31
- ::I18n::Tasks.log_verbose "run #{meth.tr('_', '-')} without arguments"
32
- cmd.send meth
33
- else
34
- opts = opts.merge(arguments: args) unless args.empty?
35
- ::I18n::Tasks.log_verbose "run #{meth.tr('_', '-')} with #{opts.map { |k, v| "#{k}=#{v}" } * ' '}"
36
- cmd.send meth, opts
29
+ begin
30
+ if opts.empty? && args.empty?
31
+ cmd.log_verbose "run #{meth.tr('_', '-')} without arguments"
32
+ cmd.send meth
33
+ else
34
+ opts = opts.merge(arguments: args) unless args.empty?
35
+ cmd.log_verbose "run #{meth.tr('_', '-')} with #{opts.map { |k, v| "#{k}=#{v}" } * ' '}"
36
+ cmd.send meth, opts
37
+ end
38
+ rescue Errno::EPIPE
39
+ # ignore Errno::EPIPE which is throw when pipe breaks, e.g.:
40
+ # i18n-tasks missing | head
37
41
  end
38
42
  else
39
43
  STDERR.puts Term::ANSIColor.red "Command unknown: #{ARGV[0]}" if ARGV[0]
data/i18n-tasks.gemspec CHANGED
@@ -12,13 +12,8 @@ Gem::Specification.new do |s|
12
12
  s.description = %q{
13
13
  i18n-tasks finds and manages missing and unused translations in your application.
14
14
 
15
- The basic approach to i18n key management in frameworks such as Rails is far from perfect.
16
- If you use a key that does not exist, this will only blow up at runtime. Keys left over from removed code accumulate
17
- in the resource files, introducing unnecessary overhead on the translators. Translation files can quickly turn to disarray.
18
-
19
- i18n-tasks improves this by using static analysis. It scans calls such as `I18n.t('some.key')` and uses this information to
20
- provide reports on key usage, missing and unused keys, and can prefill missing keys, including from Google Translate,
21
- and can also remove unused keys.
15
+ It scans calls such as `I18n.t('some.key')` and provides reports on key usage, missing, and unused keys.
16
+ It can also can pre-fill missing keys, including from Google Translate, and it can remove unused keys as well.
22
17
  }
23
18
  s.homepage = 'https://github.com/glebm/i18n-tasks'
24
19
  if s.respond_to?(:metadata=)
data/lib/i18n/tasks.rb CHANGED
@@ -13,17 +13,6 @@ require 'i18n/tasks/base_task'
13
13
 
14
14
  module I18n
15
15
  module Tasks
16
- CONFIG_FILES = %w(
17
- config/i18n-tasks.yml config/i18n-tasks.yml.erb
18
- i18n-tasks.yml i18n-tasks.yml.erb
19
- )
20
- class << self
21
- def config
22
- file = CONFIG_FILES.detect { |f| File.exists?(f) }
23
- file = YAML.load(Erubis::Eruby.new(File.read(file)).result) if file
24
- HashWithIndifferentAccess.new.merge(file.presence || {})
25
- end
26
- include ::I18n::Tasks::Logging
27
- end
16
+
28
17
  end
29
18
  end
@@ -1,33 +1,30 @@
1
1
  # coding: utf-8
2
- require 'i18n/tasks/configuration'
3
2
  require 'i18n/tasks/key_pattern_matching'
4
- require 'i18n/tasks/relative_keys'
3
+ require 'i18n/tasks/logging'
5
4
  require 'i18n/tasks/plural_keys'
6
5
  require 'i18n/tasks/used_keys'
7
- require 'i18n/tasks/translation_data'
8
6
  require 'i18n/tasks/ignore_keys'
9
7
  require 'i18n/tasks/missing_keys'
10
8
  require 'i18n/tasks/unused_keys'
11
9
  require 'i18n/tasks/google_translation'
12
10
  require 'i18n/tasks/fill_tasks'
13
- require 'i18n/tasks/logging'
11
+ require 'i18n/tasks/data'
12
+ require 'i18n/tasks/configuration'
14
13
 
15
14
  module I18n
16
15
  module Tasks
17
16
  class BaseTask
18
- include Configuration
19
17
  include KeyPatternMatching
20
- include IgnoreKeys
21
- include DataTraversal
22
- include RelativeKeys
23
18
  include PluralKeys
24
19
  include UsedKeys
20
+ include IgnoreKeys
25
21
  include MissingKeys
26
22
  include UnusedKeys
27
- include TranslationData
28
23
  include FillTasks
29
24
  include GoogleTranslation
30
25
  include Logging
26
+ include Configuration
27
+ include Data
31
28
 
32
29
  def initialize(config = {})
33
30
  self.config = config || {}
@@ -14,7 +14,7 @@ module I18n::Tasks
14
14
  on '-t', :types, 'Filter by type (types: missing_from_base, eq_base, missing_from_locale)', as: Array, delimiter: /[+:,]/, argument: true, optional: false
15
15
  end
16
16
  cmd :missing do |opt = {}|
17
- opt[:locales] = locales_opt_or_args(opt)
17
+ parse_locales! opt
18
18
  terminal_report.missing_keys i18n_task.missing_keys(opt)
19
19
  end
20
20
 
@@ -30,7 +30,7 @@ module I18n::Tasks
30
30
  end
31
31
  cmd :translate_missing do |opt = {}|
32
32
  opt[:from] = base_locale if opt[:from].blank? || opt[:from] == 'base'
33
- opt[:locales] = locales_opt_or_args(opt)
33
+ parse_locales! opt
34
34
  i18n_task.fill_missing_google_translate opt
35
35
  end
36
36
 
@@ -40,9 +40,8 @@ module I18n::Tasks
40
40
  on '-p', :placeholder, 'Value for empty keys (default: base value or key.humanize)', argument: true, optional: false
41
41
  end
42
42
  cmd :add_missing do |opt = {}|
43
- opt[:locales] = locales_opt_or_args(opt)
44
- opt[:value] ||= opt.delete(:placeholder)
45
- opt[:value] ||= proc { |key, locale|
43
+ parse_locales! opt
44
+ opt[:value] ||= opt.delete(:placeholder) || proc { |key, locale|
46
45
  # default to base value or key.humanize
47
46
  locale == base_locale && t(locale, base_locale) || key.split('.').last.to_s.humanize
48
47
  }
@@ -55,11 +54,7 @@ module I18n::Tasks
55
54
  end
56
55
  cmd :find do |opt = {}|
57
56
  opt[:filter] ||= opt.delete(:pattern) || opt[:arguments].try(:first)
58
- filter = opt[:filter].is_a?(String) ? opt[:filter].tr('+', ',') : nil
59
- used_keys = i18n_task.scanner.with_key_filter(filter) {
60
- i18n_task.used_keys true
61
- }
62
- terminal_report.used_keys used_keys
57
+ terminal_report.used_keys i18n_task.used_keys(key_filter: opt[:filter].presence, src_locations: true)
63
58
  end
64
59
 
65
60
  desc 'normalize translation data: sort and move to the right files'
@@ -67,7 +62,8 @@ module I18n::Tasks
67
62
  on '-l', :locales=, 'Locales to normalize (default: all)', on_locale_opt
68
63
  end
69
64
  cmd :normalize do |opt = {}|
70
- i18n_task.normalize_store! locales_opt_or_args(opt)
65
+ parse_locales! opt
66
+ i18n_task.normalize_store! opt[:locales]
71
67
  end
72
68
 
73
69
  desc 'remove unused keys'
@@ -75,14 +71,14 @@ module I18n::Tasks
75
71
  on '-l', :locales=, 'Locales to remove unused keys from (default: all)', on_locale_opt
76
72
  end
77
73
  cmd :remove_unused do |opt = {}|
78
- locales = locales_opt_or_args(opt)
74
+ parse_locales!(opt)
79
75
  unused_keys = i18n_task.unused_keys
80
76
  if unused_keys.present?
81
77
  terminal_report.unused_keys(unused_keys)
82
78
  unless ENV['CONFIRM']
83
- exit 1 unless agree(red "All these translations will be removed in #{bold locales * ', '}#{red '.'} " + yellow('Continue? (yes/no)') + ' ')
79
+ exit 1 unless agree(red "All these translations will be removed in #{bold opt[:locales] * ', '}#{red '.'} " + yellow('Continue? (yes/no)') + ' ')
84
80
  end
85
- i18n_task.remove_unused!(locales)
81
+ i18n_task.remove_unused!(opt[:locales])
86
82
  $stderr.puts "Removed #{unused_keys.size} keys"
87
83
  else
88
84
  $stderr.puts bold green 'No unused keys to remove'
@@ -91,7 +87,10 @@ module I18n::Tasks
91
87
 
92
88
  desc 'display i18n-tasks configuration'
93
89
  cmd :config do
94
- puts i18n_task.config_for_inspect.to_yaml.sub(/\A---\n/, '').gsub('!ruby/hash:ActiveSupport::HashWithIndifferentAccess', '')
90
+ cfg = i18n_task.config_for_inspect.to_yaml
91
+ cfg.sub! /\A---\n/, ''
92
+ cfg.gsub! /^([^\s-].+?)$/, Term::ANSIColor.cyan(Term::ANSIColor.bold('\1'))
93
+ puts cfg
95
94
  end
96
95
 
97
96
  desc 'save missing and unused translations to an Excel file'
@@ -18,8 +18,8 @@ module I18n::Tasks
18
18
  end
19
19
  end
20
20
 
21
- def locales_opt_or_args(opt)
22
- locales_opt(opt[:arguments].presence || opt[:locales]).tap do |locales|
21
+ def parse_locales!(opt)
22
+ opt[:locales] = locales_opt(opt[:arguments].presence || opt[:locales]).tap do |locales|
23
23
  log_verbose "locales for the command are #{locales.inspect}"
24
24
  end
25
25
  end
@@ -5,8 +5,18 @@ module I18n::Tasks::Configuration
5
5
  @config || (self.config = {})
6
6
  end
7
7
 
8
+ CONFIG_FILES = %w(
9
+ config/i18n-tasks.yml config/i18n-tasks.yml.erb
10
+ i18n-tasks.yml i18n-tasks.yml.erb
11
+ )
12
+ def file_config
13
+ file = CONFIG_FILES.detect { |f| File.exists?(f) }
14
+ file = YAML.load(Erubis::Eruby.new(File.read(file)).result) if file
15
+ {}.with_indifferent_access.merge(file.presence || {})
16
+ end
17
+
8
18
  def config=(conf)
9
- @config = I18n::Tasks.config.deep_merge(conf)
19
+ @config = file_config.deep_merge(conf)
10
20
  @config_sections = {}
11
21
  @config
12
22
  end
@@ -15,12 +25,10 @@ module I18n::Tasks::Configuration
15
25
  # @return [{adapter: String, options: Hash}]
16
26
  def data_config
17
27
  @config_sections[:data] ||= begin
18
- conf = (config[:data] || {}).with_indifferent_access
19
- adapter = (conf[:adapter].presence || conf[:class].presence || :file_system).to_s
20
- if adapter !~ /[A-Z]/
21
- adapter = "I18n::Tasks::Data::#{adapter.camelize}"
22
- end
23
- {adapter: adapter, options: conf.except(:adapter, :class)}
28
+ {
29
+ adapter: data.class.name,
30
+ config: data.config
31
+ }
24
32
  end
25
33
  end
26
34
 
@@ -65,6 +73,11 @@ module I18n::Tasks::Configuration
65
73
  end
66
74
  end
67
75
 
76
+ def non_base_locales(from = nil)
77
+ from ||= self.locales
78
+ Array(from) - [base_locale]
79
+ end
80
+
68
81
  # @return [String] default i18n locale
69
82
  def base_locale
70
83
  @config_sections[:base_locale] ||= (config[:base_locale] || 'en').to_s
@@ -0,0 +1,78 @@
1
+ require 'i18n/tasks/data/file_system'
2
+
3
+ module I18n::Tasks::Data
4
+ # I18n data provider
5
+ # @see I18n::Tasks::Data::FileSystem
6
+ def data
7
+ @data ||= begin
8
+ conf = (config[:data] || {}).with_indifferent_access
9
+ adapter = (conf[:adapter].presence || conf[:class].presence || :file_system).to_s
10
+ if adapter !~ /[A-Z]/
11
+ adapter = "I18n::Tasks::Data::#{adapter.camelize}"
12
+ end
13
+ adapter.constantize.new(conf.except(:adapter, :class))
14
+ end
15
+ end
16
+
17
+ def t(key, locale = base_locale)
18
+ data.t(key, locale)
19
+ end
20
+
21
+ def t_proc(locale = base_locale)
22
+ @t_proc ||= {}
23
+ @t_proc[locale] ||= proc { |key| t(key, locale) }
24
+ end
25
+
26
+ # whether the value for key exists in locale (defaults: base_locale)
27
+ def key_value?(key, locale = base_locale)
28
+ t(key, locale).present?
29
+ end
30
+
31
+ # write to store, normalizing all data
32
+ def normalize_store!(from = nil)
33
+ from = self.locales unless from
34
+ Array(from).each do |target_locale|
35
+ # the store itself handles normalization
36
+ data[target_locale] = data[target_locale]
37
+ end
38
+ end
39
+
40
+ # if :locales option present, call update_locale_data for each locale
41
+ # otherwise, call update_locale_data for :locale option or base locale
42
+ # @option opts [Array] :locales
43
+ # @option opts [String] :locale
44
+ def update_data(opts = {})
45
+ if opts.key?(:locales)
46
+ locales = (Array(opts[:locales]).presence || self.locales).map(&:to_s)
47
+ # make sure base_locale always comes first if present
48
+ locales = [base_locale] + (locales - [base_locale]) if locales.include?(base_locale)
49
+ opts = opts.except(:locales)
50
+ locales.each { |locale| update_locale_data(locale, opts.merge(locale: locale)) }
51
+ else
52
+ update_locale_data(opts[:locale] || base_locale, opts)
53
+ end
54
+ end
55
+
56
+ # @param locale
57
+ # @option opts [Array|Proc] :keys keys to update, if proc call with locale
58
+ # @option opts [String|Proc] value, if proc call with each key
59
+ # @option opts [String|Proc] values, if proc call with all the keys
60
+ def update_locale_data(locale, opts = {})
61
+ keys = opts[:keys]
62
+ keys = keys.call(locale) if keys.respond_to?(:call)
63
+ return if keys.empty?
64
+
65
+ values = opts[:values]
66
+ values = values.call(keys, locale) if values.respond_to?(:call)
67
+ values ||= begin
68
+ value = opts[:value] or raise 'pass value or values'
69
+ if value.respond_to?(:call)
70
+ keys.map { |key| value.call(key, locale) }
71
+ else
72
+ [value] * keys.size
73
+ end
74
+ end
75
+ data[locale] += keys.map(&:to_s).zip(values)
76
+ end
77
+
78
+ end
@@ -0,0 +1,41 @@
1
+ module I18n
2
+ module Tasks
3
+ module Data
4
+ module FileFormats
5
+ def self.included(base)
6
+ base.extend ClassMethods
7
+ end
8
+
9
+ delegate :adapter_for, to: :class
10
+
11
+ protected
12
+
13
+ def load_file(file)
14
+ adapter_for(file).parse(
15
+ ::File.read(file)
16
+ )
17
+ end
18
+
19
+ def write_tree(path, tree)
20
+ ::File.open(path, 'w') { |f|
21
+ f.write(adapter_for(path).dump(tree.to_hash))
22
+ }
23
+ end
24
+
25
+ module ClassMethods
26
+ # @param pattern [String] File.fnmatch pattern
27
+ # @param adapter [responds to parse(string)->hash and dump(hash)->string]
28
+ def register_adapter(pattern, adapter)
29
+ (@fn_patterns ||= {})[pattern] = adapter
30
+ end
31
+
32
+ def adapter_for(path)
33
+ @fn_patterns.detect { |pattern, adapter|
34
+ ::File.fnmatch(pattern, path)
35
+ }[1] or raise "Adapter not found for #{path}. Registered adapters: #{@fn_patterns.inspect}"
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
@@ -1,11 +1,10 @@
1
- require 'i18n/tasks/data/storage/file_storage'
1
+ require 'i18n/tasks/data/file_system_base'
2
2
  require 'i18n/tasks/data/adapter/json_adapter'
3
3
  require 'i18n/tasks/data/adapter/yaml_adapter'
4
4
 
5
5
  module I18n::Tasks
6
6
  module Data
7
- class FileSystem
8
- include Storage::FileStorage
7
+ class FileSystem < FileSystemBase
9
8
  register_adapter '*.yml', Adapter::YamlAdapter
10
9
  register_adapter '*.json', Adapter::JsonAdapter
11
10
  end