i18n-tasks 0.3.6 → 0.3.7

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