i18n-tasks 0.2.1 → 0.2.2

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: 191973eaa9d7a29ea1b149eef1ce61c2d79f8c4c
4
- data.tar.gz: 8cdd8e2e5408577ce0abf3b7d78bb90c43834bd2
3
+ metadata.gz: 938b362fb2bb4551507b503b2230e7175eff7a97
4
+ data.tar.gz: 378887ed86ebbf0cffc511d4a5b6ad83627d690f
5
5
  SHA512:
6
- metadata.gz: acefe02918495e2091ac389e985491eb14542f7b54f94e8301d1aa0333e814b69ddc361d796f1a898084ddd48ab1393650cc34102e71c16a1f8917d1e2d2d741
7
- data.tar.gz: 53c49f375246c3dc38e7c63b9a8ed27e99cb42fc2f709557cdf63040d6d4635b7ad7cff2c165887adbba042ef44e853dc6b08110af63f947eff890dea0d67603
6
+ metadata.gz: 182391f6acbb385bf294cf675debfef292153309bec9beacda7b5f8d6e71b178e21d2a111d7bdf31aec843bc1ec9d7fac53fd1a2a59a098132e887c7653f9de0
7
+ data.tar.gz: 3f0701378d7ecea4be0f252feae1a59a3ca9f89beeb5f6e14cd0a0f9f8ca6d807bb13ced370425d612c23bfb7c76627eef5509068cf6fc2267e699596cbae2a9
data/CHANGES.md CHANGED
@@ -1,3 +1,11 @@
1
+ ## v0.2.2
2
+
3
+ * improved output with terminal-table (@glebm)
4
+
5
+ ## v0.2.1
6
+
7
+ * fill tasks renamed, fix symbol key search (@glebm)
8
+
1
9
  ## v0.2.0
2
10
 
3
11
  * 3 more prefill tasks, including Google Translate (@glebm)
data/README.md CHANGED
@@ -1,7 +1,7 @@
1
1
  # i18n-tasks [![Build Status](https://travis-ci.org/glebm/i18n-tasks.png?branch=master)](https://travis-ci.org/glebm/i18n-tasks) [![Code Climate](https://codeclimate.com/github/glebm/i18n-tasks.png)](https://codeclimate.com/github/glebm/i18n-tasks)
2
2
 
3
3
 
4
- Rails I18n tasks to find missing / unused translations and more. Works with slim / coffee / haml etc.
4
+ Tasks to manage missing and unused translations in Rails.
5
5
 
6
6
  ![i18n-screenshot](https://raw.github.com/glebm/i18n-tasks/master/doc/img/i18n-tasks.gif "i18n-tasks output screenshot")
7
7
 
@@ -63,7 +63,7 @@ For more examples see [the tests](/spec/i18n_tasks_spec.rb).
63
63
  Simply add to Gemfile:
64
64
 
65
65
  ```ruby
66
- gem 'i18n-tasks', '~> 0.2.1'
66
+ gem 'i18n-tasks', '~> 0.2.2'
67
67
  ```
68
68
 
69
69
  ## Configuration
@@ -117,8 +117,8 @@ search:
117
117
  # explicitly exclude files (default: blank = exclude no files)
118
118
  exclude:
119
119
  - '*.js'
120
- # you can override the default grep pattern:
121
- pattern: "\\bt[( ]\\s*(.)((?<=\").+?(?=\")|(?<=').+?(?=')|(?<=:)\\w+\\b)"
120
+ # you can override the default key regex pattern:
121
+ pattern: "\\bt[( ]\\s*(:?\".+?\"|:?'.+?'|:\\w+)"
122
122
  ```
123
123
 
124
124
  ### Fine-tuning
Binary file
@@ -8,8 +8,11 @@ Gem::Specification.new do |spec|
8
8
  spec.version = I18n::Tasks::VERSION
9
9
  spec.authors = ['glebm']
10
10
  spec.email = ['glex.spb@gmail.com']
11
- spec.summary = %q{Find all the missing and unused translations!}
12
- spec.description = %q{Rails I18n tasks to find missing / unused translations and more}
11
+ spec.summary = %q{Tasks to manage missing and unused translations in Rails.}
12
+ spec.description = %q{
13
+ rake tasks to find unused and missing translations, normalize locale files,
14
+ and prefill missing keys. Supports relative and plural keys and Google Translate.
15
+ }
13
16
  spec.homepage = 'https://github.com/glebm/i18n-tasks'
14
17
  spec.license = 'MIT'
15
18
 
@@ -22,6 +25,7 @@ Gem::Specification.new do |spec|
22
25
  spec.add_dependency 'activesupport'
23
26
  spec.add_dependency 'easy_translate'
24
27
  spec.add_dependency 'term-ansicolor'
28
+ spec.add_dependency 'terminal-table'
25
29
  spec.add_development_dependency 'bundler', '~> 1.3'
26
30
  spec.add_development_dependency 'rake'
27
31
  spec.add_development_dependency 'rspec-rails'
@@ -3,6 +3,7 @@ require 'i18n/tasks/railtie'
3
3
  require 'active_support/core_ext/hash'
4
4
  require 'active_support/core_ext/string'
5
5
  require 'active_support/core_ext/module/delegation'
6
+ require 'active_support/core_ext/object/try'
6
7
  require 'term/ansicolor'
7
8
 
8
9
  module I18n
@@ -1,19 +1,24 @@
1
1
  # coding: utf-8
2
+ require 'i18n/tasks/configuration'
2
3
  require 'i18n/tasks/key_pattern_matching'
3
4
  require 'i18n/tasks/relative_keys'
4
5
  require 'i18n/tasks/plural_keys'
5
6
  require 'i18n/tasks/source_keys'
6
7
  require 'i18n/tasks/translation_data'
7
- require 'i18n/tasks/translation'
8
8
  require 'i18n/tasks/ignore_keys'
9
9
  require 'i18n/tasks/missing_keys'
10
10
  require 'i18n/tasks/untranslated_keys'
11
11
  require 'i18n/tasks/unused_keys'
12
+ require 'i18n/tasks/google_translation'
13
+ require 'i18n/tasks/fill_tasks'
12
14
 
13
15
  module I18n
14
16
  module Tasks
15
17
  class BaseTask
18
+ include Configuration
16
19
  include KeyPatternMatching
20
+ include IgnoreKeys
21
+ include DataTraversal
17
22
  include RelativeKeys
18
23
  include PluralKeys
19
24
  include SourceKeys
@@ -21,19 +26,8 @@ module I18n
21
26
  include UntranslatedKeys
22
27
  include UnusedKeys
23
28
  include TranslationData
24
- include Translation
25
- include IgnoreKeys
26
-
27
- # i18n-tasks config (defaults + config/i18n-tasks.yml)
28
- # @return [Hash{String => String,Hash,Array}]
29
- def config
30
- I18n::Tasks.config
31
- end
32
-
33
- def warn_deprecated(message)
34
- I18n::Tasks.warn_deprecated(message)
35
- end
36
-
29
+ include FillTasks
30
+ include GoogleTranslation
37
31
  end
38
32
  end
39
33
  end
@@ -0,0 +1,38 @@
1
+ module I18n::Tasks::Configuration
2
+ extend ActiveSupport::Concern
3
+
4
+ # i18n-tasks config (defaults + config/i18n-tasks.yml)
5
+ # @return [Hash{String => String,Hash,Array}]
6
+ def config
7
+ @config ||= I18n::Tasks.config
8
+ end
9
+
10
+ DEFAULT_PATTERN = /\bt[( ]\s*(:?".+?"|:?'.+?'|:\w+)/
11
+ # search config
12
+ # @return [Hash{String => String,Hash,Array}]
13
+ def search_config
14
+ @search_config ||= begin
15
+ if config.key?(:grep)
16
+ config[:search] ||= config.delete(:grep)
17
+ I18n::Tasks.warn_deprecated 'please rename "grep" key to "search" in config/i18n-tasks.yml'
18
+ end
19
+ search_config = (config[:search] || {}).with_indifferent_access
20
+ search_config.tap do |conf|
21
+ conf[:paths] = %w(app/) if conf[:paths].blank?
22
+ conf[:include] = Array(conf[:include]) if conf[:include].present?
23
+ conf[:exclude] = Array(conf[:exclude])
24
+ conf[:pattern] = conf[:pattern].present? ? Regexp.new(conf[:pattern]) : DEFAULT_PATTERN
25
+ end
26
+ end
27
+ end
28
+
29
+ # translation config
30
+ # @return [Hash{String => String,Hash,Array}]
31
+ def translation_config
32
+ @translation_config ||= begin
33
+ conf = (config[:translation] ||= {}).with_indifferent_access
34
+ conf[:api_key] ||= ENV['GOOGLE_TRANSLATE_API_KEY']
35
+ conf
36
+ end
37
+ end
38
+ end
@@ -2,11 +2,16 @@ module I18n::Tasks::DataTraversal
2
2
  # translation of the key found in the passed hash or nil
3
3
  # @return [String,nil]
4
4
  def t(hash = data[base_locale], key)
5
+ if hash.is_a?(String)
6
+ # has is a locale
7
+ raise ArgumentError.new("invalid locale: #{hash}") if hash =~ /\W/
8
+ hash = data[hash]
9
+ end
5
10
  key.split('.').inject(hash) { |r, seg| r[seg] if r }
6
11
  end
7
12
 
8
- # traverse => flat_map
9
- def traverse_flat_map(hash)
13
+ # traverse => map if yield(k, v)
14
+ def traverse_map_if(hash)
10
15
  list = []
11
16
  traverse hash do |k, v|
12
17
  mapped = yield(k, v)
@@ -0,0 +1,54 @@
1
+ module I18n::Tasks::FillTasks
2
+ def add_missing!(locale = base_locale, placeholder = nil)
3
+ normalize_store! locale
4
+ set_blank_values! locale, locale == base_locale do |keys|
5
+ keys.map { |key|
6
+ placeholder || key.split('.').last.to_s.humanize
7
+ }
8
+ end
9
+ end
10
+
11
+ def fill_with_blanks!(locales = nil)
12
+ locales = non_base_locales(locales)
13
+ add_missing! base_locale, ''
14
+ normalize_store! locales
15
+ locales.each do |locale|
16
+ add_missing! locale, ''
17
+ end
18
+ end
19
+
20
+ def fill_with_google_translate!(locales = nil)
21
+ normalize_store! base_locale
22
+ locales = non_base_locales(locales)
23
+ normalize_store! locales
24
+ locales.each do |locale|
25
+ set_blank_values! locale do |blank_keys|
26
+ google_translate blank_keys.map { |k| t(k) }, to: locale, from: base_locale
27
+ end
28
+ end
29
+ end
30
+
31
+ def fill_with_base_values!(locales = nil)
32
+ normalize_store! base_locale
33
+ locales = non_base_locales(locales)
34
+ normalize_store! locales
35
+ locales.each do |locale|
36
+ set_blank_values! locale do |blank_keys|
37
+ blank_keys.map { |k| t(k) }
38
+ end
39
+ end
40
+ end
41
+
42
+ # fill blank values with values from passed block
43
+ # @param [String] locale
44
+ # @param [true, false] include_missing Include keys present in source but missing from base locale
45
+ def set_blank_values!(locale = base_locale, include_missing = (locale == base_locale), &fill_with)
46
+ blank_keys = traverse_map_if(data[base_locale]) { |key, value|
47
+ key if !key_value?(key, locale) && !ignore_key?(key, :missing)
48
+ }
49
+ blank_keys += keys_missing_from_base if include_missing
50
+
51
+ list = blank_keys.uniq.zip fill_with.call(blank_keys)
52
+ data[locale] = data[locale].deep_merge(list_to_tree(list))
53
+ end
54
+ end
@@ -1,6 +1,6 @@
1
1
  require 'easy_translate'
2
2
 
3
- module I18n::Tasks::Translation
3
+ module I18n::Tasks::GoogleTranslation
4
4
  def google_translate(strings, opts)
5
5
  return [] if strings.empty?
6
6
  opts = opts.dup
@@ -9,12 +9,4 @@ module I18n::Tasks::Translation
9
9
  end
10
10
  EasyTranslate.translate strings, opts
11
11
  end
12
-
13
- def translation_config
14
- @translation_config ||= begin
15
- conf = (config[:translation] ||= {}).with_indifferent_access
16
- conf[:api_key] ||= ENV['GOOGLE_TRANSLATE_API_KEY']
17
- conf
18
- end
19
- end
20
12
  end
@@ -1,6 +1,7 @@
1
1
  module I18n::Tasks::IgnoreKeys
2
- # whether to ignore the key. ignore_type one of :missing, :eq_base, :blank, :unused.
3
- # will apply global ignore rules as well
2
+ # whether to ignore the key
3
+ # will also apply global ignore rules
4
+ # @param [:missing, :eq_base, :blank, :unused] ignore_type
4
5
  def ignore_key?(key, ignore_type, locale = nil)
5
6
  key =~ ignore_pattern(ignore_type, locale)
6
7
  end
@@ -14,7 +14,7 @@ module I18n::Tasks::KeyPatternMatching
14
14
  # convert key.* to key\..*
15
15
  def key_pattern_to_re(key_pattern)
16
16
  if key_pattern.end_with? '.'
17
- warn_deprecated %Q(please change pattern "#{key_pattern}" to "#{key_pattern += '*'}" in config/i18n-tasks.yml)
17
+ I18n::Tasks.warn_deprecated %Q(please change pattern "#{key_pattern}" to "#{key_pattern += '*'}" in config/i18n-tasks.yml)
18
18
  end
19
19
  /^#{key_pattern.
20
20
  gsub(/\./, '\.').
@@ -1,33 +1,23 @@
1
1
  module I18n::Tasks::MissingKeys
2
- # missing keys, i.e. key that are in the code but are not in the base locale data
3
- # @return Array{Hash}
4
- def keys_missing_base_value
2
+ # @return Array missing keys, i.e. key that are in the code but are not in the base locale data
3
+ def keys_missing_from_base
5
4
  find_source_keys.reject { |key|
6
5
  key_value?(key, base_locale) || pattern_key?(key) || ignore_key?(key, :missing)
7
- }.map { |key| {locale: base_locale, type: :none, key: key} }
6
+ }
8
7
  end
9
8
 
10
- # present in base locale, but untranslated in another locale
11
- # @return Array{Hash}
12
- def keys_missing_translation(locale)
13
- trn = data[locale]
14
- r = []
15
- traverse data[base_locale] do |key, base_value|
16
- value_in_locale = t(trn, key)
17
- if value_in_locale.blank? && !ignore_key?(key, :missing)
18
- r << {locale: locale, key: key, type: :blank, base_value: base_value}
19
- elsif value_in_locale == base_value && !ignore_key?(key, :eq_base, locale)
20
- r << {locale: locale, key: key, type: :eq_base, base_value: base_value}
21
- end
9
+ # @return Array keys missing value (but present in base)
10
+ def keys_missing_value(locale)
11
+ traverse_map_if data[base_locale] do |key, base_value|
12
+ key if !key_value?(key, locale) && !ignore_key?(key, :missing)
22
13
  end
23
- r
24
14
  end
25
15
 
26
- # sort first by locale, then by type
27
- # @return Array{Hash}
28
- def sort_keys(keys)
29
- keys.sort { |a, b|
30
- (l = a[:locale] <=> b[:locale]).zero? ? a[:type] <=> b[:type] : l
31
- }
16
+ # @return Array keys missing value (but present in base)
17
+ def keys_where_value_eq_base(locale)
18
+ traverse_map_if data[base_locale] do |key, base_value|
19
+ key if base_value == t(locale, key) && !ignore_key?(key, :eq_base, locale)
20
+ end
32
21
  end
22
+
33
23
  end
@@ -1,13 +1,13 @@
1
1
  module I18n::Tasks::PluralKeys
2
2
  PLURAL_KEY_RE = /\.(?:zero|one|two|few|many|other)$/
3
3
 
4
- # @param key [String] i18n key
5
- # @param data [Hash{String => String,Hash}] locale data
4
+ # @param [String] key i18n key
5
+ # @param [String] locale to pull key data from
6
6
  # @return the base form if the key is a specific plural form (e.g. apple for apple.many), and the key as passed otherwise
7
- def depluralize_key(key, data)
8
- return key if key !~ PLURAL_KEY_RE || t(data, key).is_a?(Hash)
7
+ def depluralize_key(locale = base_locale, key)
8
+ return key if key !~ PLURAL_KEY_RE || t(locale, key).is_a?(Hash)
9
9
  parent_key = key.split('.')[0..-2] * '.'
10
- plural_versions = t(data, parent_key)
10
+ plural_versions = t(locale, parent_key)
11
11
  if plural_versions.is_a?(Hash) && plural_versions.all? { |k, v| ".#{k}" =~ PLURAL_KEY_RE && !v.is_a?(Hash) }
12
12
  parent_key
13
13
  else
@@ -3,7 +3,7 @@ module I18n
3
3
  module Tasks
4
4
  class Railtie < ::Rails::Railtie
5
5
  rake_tasks {
6
- load "tasks/i18n-tasks.rake"
6
+ load 'tasks/i18n-tasks.rake'
7
7
  }
8
8
  end
9
9
  end
@@ -0,0 +1,86 @@
1
+ # coding: utf-8
2
+ require 'terminal-table'
3
+ module I18n
4
+ module Tasks
5
+ module Reports
6
+ class Terminal
7
+ include Term::ANSIColor
8
+
9
+ def initialize
10
+ @task = I18n::Tasks::BaseTask.new
11
+ end
12
+
13
+ attr_reader :task
14
+
15
+ def missing_translations
16
+ recs = task.untranslated_keys
17
+ print_title "Missing keys and translations (#{recs.length})"
18
+ if recs.present?
19
+ type_sym = {
20
+ :none => red('✗'),
21
+ :blank => yellow('∅'),
22
+ :eq_base => bold(blue('='))
23
+ }
24
+ type_desc = {
25
+ :none => 'key missing',
26
+ :blank => 'translation blank',
27
+ :eq_base => 'value same as base value'
28
+ }
29
+ $stderr.puts "#{bold 'Types:'} #{type_desc.keys.map { |k| "#{type_sym[k]} #{type_desc[k]}" } * ', '}"
30
+ print_table headings: [magenta(bold('Locale')), bold('Type'), magenta('i18n Key'), bold(cyan "Base value (#{base_locale})")] do |t|
31
+ t.rows = recs.map { |rec|
32
+ if rec[:type] == :none
33
+ locale = magenta bold rec[:locale]
34
+ base_value = ''
35
+ else
36
+ locale = magenta rec[:locale]
37
+ base_value = cyan rec[:base_value].try(:strip) || ''
38
+ end
39
+ [{:value => locale, :alignment => :center },
40
+ {:value => type_sym[rec[:type]], :alignment => :center },
41
+ magenta(rec[:key]),
42
+ base_value]
43
+ }
44
+ end
45
+ else
46
+ print_success 'Good job! No translations missing!'
47
+ end
48
+ end
49
+
50
+ def unused_translations
51
+ unused = task.unused_keys
52
+ print_title "Unused i18n keys (#{unused.length})"
53
+ if unused.present?
54
+ print_table headings: [bold(magenta('i18n Key')), cyan("Base value (#{base_locale})")] do |t|
55
+ t.rows = unused.map { |x| [magenta(x[0]), cyan(x[1])] }
56
+ end
57
+ else
58
+ print_success 'Good job! Every translation is used!'
59
+ end
60
+ end
61
+
62
+ private
63
+
64
+ extend Term::ANSIColor
65
+
66
+ def print_title(title)
67
+ $stderr.puts "#{bold cyan title.strip} #{dark "|"} #{bold "i18n-tasks v#{I18n::Tasks::VERSION}"}"
68
+ end
69
+
70
+ def print_success(message)
71
+ $stderr.puts(bold green message)
72
+ end
73
+
74
+ private
75
+ def indent(txt, n = 2)
76
+ spaces = ' ' * n
77
+ txt.gsub /^/, spaces
78
+ end
79
+
80
+ def print_table(opts, &block)
81
+ puts ::Terminal::Table.new(opts, &block)
82
+ end
83
+ end
84
+ end
85
+ end
86
+ end
@@ -1,8 +1,6 @@
1
1
  require 'find'
2
2
 
3
3
  module I18n::Tasks::SourceKeys
4
- DEFAULT_PATTERN = /\bt[( ]\s*(:?".+?"|:?'.+?'|:\w+)/
5
-
6
4
  # find all keys in the source (relative keys are returned in absolutized)
7
5
  # @return [Array<String>]
8
6
  def find_source_keys
@@ -29,24 +27,6 @@ module I18n::Tasks::SourceKeys
29
27
  find_source_keys.select { |k| k =~ /\#{.*?}/ || k.ends_with?('.') }.map { |k| k.split(/\.?#/)[0].presence }.compact
30
28
  end
31
29
 
32
- # grep config, also from config/i18n-tasks.yml
33
- # @return [Hash{String => String,Hash,Array}]
34
- def search_config
35
- @search_config ||= begin
36
- if config.key?(:grep)
37
- config[:search] ||= config.delete(:grep)
38
- warn_deprecated 'please rename "grep" key to "search" in config/i18n-tasks.yml'
39
- end
40
- search_config = (config[:search] || {}).with_indifferent_access
41
- search_config.tap do |conf|
42
- conf[:paths] = %w(app/) if conf[:paths].blank?
43
- conf[:include] = Array(conf[:include]) if conf[:include].present?
44
- conf[:exclude] = Array(conf[:exclude])
45
- conf[:pattern] ||= conf[:pattern].present? ? Regexp.new(conf[:pattern]) : DEFAULT_PATTERN
46
- end
47
- end
48
- end
49
-
50
30
  # Run given block for every relevant file, according to search_config
51
31
  # @return [Array] Results of block calls
52
32
  def traverse_files
@@ -1,8 +1,6 @@
1
- require 'i18n/tasks/data_traversal'
2
1
  require 'i18n/tasks/data/yaml'
3
2
 
4
3
  module I18n::Tasks::TranslationData
5
- include I18n::Tasks::DataTraversal
6
4
 
7
5
  # I18n data provider
8
6
  # @see I18n::Tasks::Data::Yaml
@@ -21,39 +19,27 @@ module I18n::Tasks::TranslationData
21
19
  t(data[locale], key).present?
22
20
  end
23
21
 
22
+ # @return [Array<String>] all available locales
23
+ def locales
24
+ config[:locales] ||= I18n.available_locales.map(&:to_s)
25
+ end
26
+
24
27
  # @return [String] default i18n locale
25
28
  def base_locale
26
29
  config[:base_locale] ||= I18n.default_locale.to_s
27
30
  end
28
31
 
29
- # @return [Array<String>] all available locales
30
- def locales
31
- config[:locales] ||= I18n.available_locales.map(&:to_s)
32
+ def non_base_locales(from = nil)
33
+ from = self.locales unless from.present?
34
+ Array(from) - [base_locale]
32
35
  end
33
36
 
34
37
  # write to store, normalizing all data
35
- def normalize_store!(locales = self.locales)
36
- Array(locales).each do |target_locale|
38
+ def normalize_store!(from = nil)
39
+ from = self.locales unless from.present?
40
+ Array(from).each do |target_locale|
37
41
  # the store itself handles normalization
38
42
  data[target_locale] = data[target_locale]
39
43
  end
40
44
  end
41
-
42
- # fill missing / blank keys with values from passed block
43
- def fill_blanks!(locale = base_locale, &fill_with)
44
- blank_keys =
45
- if locale == base_locale
46
- # for base locale "blank" is: present in source but not in the base locale.
47
- keys_missing_base_value.map { |e| e[:key] } +
48
- traverse_flat_map(data[base_locale]) { |key, value|
49
- key if value.to_s.blank? && !ignore_key?(key, :missing) }
50
- else
51
- # for other locales "blank" is: present in base but not in the locale itself.
52
- traverse_flat_map(data[base_locale]) { |key|
53
- key if !key_value?(key, locale) && !ignore_key?(key, :missing) }
54
- end
55
-
56
- list = blank_keys.uniq.zip fill_with.call(blank_keys)
57
- data[locale] = data[locale].deep_merge(list_to_tree(list))
58
- end
59
45
  end
@@ -6,7 +6,20 @@ module I18n::Tasks::UntranslatedKeys
6
6
  # :base_value — translation value in base locale if one is present
7
7
  # @return [Array<Hash{Symbol => String,Symbol,nil}>]
8
8
  def untranslated_keys
9
- other_locales = locales - [base_locale]
10
- sort_keys keys_missing_base_value + other_locales.map { |locale| keys_missing_translation(locale) }.flatten(1)
9
+ keys = keys_missing_from_base.map { |key| {locale: base_locale, key: key, type: :none} } +
10
+ non_base_locales.map { |locale|
11
+ keys_missing_value(locale).map { |key| {locale: locale, key: key, type: :blank, base_value: t(base_locale, key)}} + keys_where_value_eq_base(locale).map { |key|
12
+ {locale: locale, key: key, type: :eq_base, base_value: t(base_locale, key)}
13
+ }
14
+ }.flatten
15
+ sort_keys keys
16
+ end
17
+
18
+ # sort first by locale, then by type
19
+ # @return Array{Hash}
20
+ def sort_keys(keys)
21
+ keys.sort { |a, b|
22
+ (l = a[:locale] <=> b[:locale]).zero? ? a[:type] <=> b[:type] : l
23
+ }
11
24
  end
12
25
  end
@@ -4,15 +4,12 @@ module I18n
4
4
  module Tasks
5
5
  module UnusedKeys
6
6
  # @return [Array<[String, String]>] all the unused translations as an array of [key, value] pairs
7
- def unused_keys
8
- r = []
9
- d = self.data[base_locale]
10
- traverse d do |key, value|
7
+ def unused_keys(locale = base_locale)
8
+ traverse_map_if data[locale] do |key, value|
11
9
  next if pattern_key?(key) || ignore_key?(key, :unused)
12
- key = depluralize_key(key, d)
13
- r << [key, value] unless used_key?(key)
14
- end
15
- r.uniq
10
+ key = depluralize_key(locale, key)
11
+ [key, value] unless used_key?(key)
12
+ end.uniq
16
13
  end
17
14
  end
18
15
  end
@@ -1,5 +1,5 @@
1
1
  module I18n
2
2
  module Tasks
3
- VERSION = '0.2.1'
3
+ VERSION = '0.2.2'
4
4
  end
5
5
  end
@@ -1,95 +1,72 @@
1
1
  require 'set'
2
2
  require 'i18n/tasks/base_task'
3
- require 'i18n/tasks/output/terminal'
3
+ require 'i18n/tasks/reports/terminal'
4
+ require 'active_support/core_ext/module/delegation'
4
5
 
5
6
  namespace :i18n do
6
7
  desc 'show missing translations'
7
8
  task :missing => 'i18n:setup' do
8
- term_output.missing i18n_tasks.untranslated_keys
9
+ report.missing_translations
9
10
  end
10
11
 
11
12
  desc 'show unused translations'
12
13
  task :unused => 'i18n:setup' do
13
- term_output.unused i18n_tasks.unused_keys
14
+ report.unused_translations
14
15
  end
15
16
 
16
17
  desc 'normalize translation data: sort and move to the right files'
17
- task :normalize => 'i18n:setup' do
18
- normalize_store!
18
+ task :normalize, [:locales] => 'i18n:setup' do |t, args|
19
+ i18n_tasks.normalize_store! args[:locales]
19
20
  end
20
21
 
21
22
  desc 'add <key: placeholder || key.humanize> to the base locale'
22
23
  task :add_missing, [:placeholder] => 'i18n:setup' do |t, args|
23
- normalize_store!
24
- i18n_tasks.fill_blanks!(base_locale) { |keys|
25
- keys.map { |key|
26
- args[:placeholder] || key.split('.').last.to_s.humanize
27
- }
28
- }
24
+ i18n_tasks.add_missing! base_locale, args[:placeholder]
29
25
  end
30
26
 
31
-
32
27
  desc 'fill translations with values'
33
28
  namespace :fill do
34
29
 
35
30
  desc 'add <key: ""> to each locale'
36
31
  task :blanks, [:locales] => 'i18n:setup' do |t, args|
37
- normalize_store!
38
- [base_locale, *non_base_locales].each do |locale|
39
- fill_blanks!(locale) { |blank_keys| blank_keys.map { '' } }
40
- end
32
+ i18n_tasks.fill_with_blanks! parse_locales args[:locales]
41
33
  end
42
34
 
43
35
  desc 'add <key: Google Translated value> to each non-base locale, uses env GOOGLE_TRANSLATE_API_KEY'
44
36
  task :google_translate, [:locales] => 'i18n:setup' do |t, args|
45
- normalize_store!
46
- non_base_locales(args).each do |locale|
47
- fill_blanks!(locale) { |blank_keys|
48
- i18n_tasks.google_translate blank_keys.map { |k| t(k) }, to: locale, from: base_locale
49
- }
50
- end
37
+ i18n_tasks.fill_with_google_translate! parse_locales args[:locales]
51
38
  end
52
39
 
53
40
  desc 'add <key: base value> to each non-base locale'
54
41
  task :base_value, [:locales] => 'i18n:setup' do |t, args|
55
- normalize_store!
56
- non_base_locales(args).each do |locale|
57
- fill_blanks!(locale) { |blank_keys| blank_keys.map { |k| t(k) } }
58
- end
42
+ i18n_tasks.fill_with_base_values! parse_locales args[:locales]
59
43
  end
60
44
  end
61
45
 
62
46
  task 'i18n:setup' => :environment do
63
47
  if File.exists?('.i18nignore')
64
- STDERR.puts 'Looks like you are using .i18ignore. It is no longer used in favour of config/i18n-tasks.yml.'
65
- STDERR.puts 'See README.md https://github.com/glebm/i18n-tasks'
48
+ I18n::Tasks.warn_deprecated "Looks like you are using .i18ignore. It is no longer used in favour of config/i18n-tasks.yml.\n
49
+ See README.md https://github.com/glebm/i18n-tasks"
66
50
  end
67
51
  end
68
52
 
69
53
  module I18n::Tasks::RakeHelpers
70
- extend ActiveSupport::Concern
71
-
72
- included do
73
- delegate :t, :locales, :base_locale, :normalize_store!, :fill_blanks!, to: :i18n_tasks
54
+ include Term::ANSIColor
74
55
 
75
- def i18n_tasks
76
- @i18n_tasks ||= I18n::Tasks::BaseTask.new
77
- end
56
+ delegate :base_locale, to: :i18n_tasks
78
57
 
79
- def term_output
80
- @term_output ||= I18n::Tasks::Output::Terminal.new
81
- end
58
+ def i18n_tasks
59
+ @i18n_tasks ||= I18n::Tasks::BaseTask.new
60
+ end
82
61
 
83
- def non_base_locales(args = nil)
84
- locales_or_all(args) - [base_locale]
85
- end
62
+ def report
63
+ @report ||= I18n::Tasks::Reports::Terminal.new
64
+ end
86
65
 
87
- def locales_or_all(args = nil)
88
- args[:locales] ? args[:locales].strip.split(/\s*\+\s*/) : locales
89
- end
66
+ def parse_locales(arg = nil)
67
+ arg.try(:strip).try(:split, /\s*\+\s*/).try(:compact)
90
68
  end
91
69
  end
92
-
93
70
  include I18n::Tasks::RakeHelpers
94
- include Term::ANSIColor
95
71
  end
72
+
@@ -17,7 +17,8 @@ describe 'rake i18n' do
17
17
  describe 'unused' do
18
18
  it 'detects unused' do
19
19
  TestCodebase.capture_stderr do
20
- TestCodebase.rake_result('i18n:unused').should be_i18n_keys %w(unused.a unused.numeric unused.plural)
20
+ out = TestCodebase.rake_result('i18n:unused')
21
+ out.should be_i18n_keys %w(unused.a unused.numeric unused.plural)
21
22
  end.should =~ /Unused i18n keys \(3\)/
22
23
  end
23
24
  end
@@ -5,9 +5,12 @@ RSpec::Matchers.define :be_i18n_keys do |expected|
5
5
  end
6
6
 
7
7
  def extract_keys(actual)
8
- actual = actual.split("\n").map(&:presence).compact.map { |row|
9
- row.gsub(/\s+/, ' ').strip.split(' ').map(&:presence).compact
8
+ actual = actual.split("\n").map(&:presence).compact
9
+ actual = actual[3..-2] if actual[0] = /^\s*[+-]+\s*$/
10
+ actual = actual.map { |row|
11
+ row.gsub(/(?:\s|^)\|(?:\s|$)/, ' ').gsub(/\s+/, ' ').strip.split(' ').map(&:presence).compact
10
12
  }
13
+ return [] if actual.empty?
11
14
  if actual[0][1] =~ /[✗∅=]/
12
15
  locale_col = 0
13
16
  key_col = 2
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.2.1
4
+ version: 0.2.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - glebm
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2013-11-23 00:00:00.000000000 Z
11
+ date: 2013-11-24 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rake
@@ -66,6 +66,20 @@ dependencies:
66
66
  - - '>='
67
67
  - !ruby/object:Gem::Version
68
68
  version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: terminal-table
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - '>='
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :runtime
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - '>='
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
69
83
  - !ruby/object:Gem::Dependency
70
84
  name: bundler
71
85
  requirement: !ruby/object:Gem::Requirement
@@ -122,7 +136,10 @@ dependencies:
122
136
  - - '>='
123
137
  - !ruby/object:Gem::Version
124
138
  version: '0'
125
- description: Rails I18n tasks to find missing / unused translations and more
139
+ description: |2
140
+
141
+ rake tasks to find unused and missing translations, normalize locale files,
142
+ and prefill missing keys. Supports relative and plural keys and Google Translate.
126
143
  email:
127
144
  - glex.spb@gmail.com
128
145
  executables: []
@@ -140,17 +157,19 @@ files:
140
157
  - i18n-tasks.gemspec
141
158
  - lib/i18n/tasks.rb
142
159
  - lib/i18n/tasks/base_task.rb
160
+ - lib/i18n/tasks/configuration.rb
143
161
  - lib/i18n/tasks/data/yaml.rb
144
162
  - lib/i18n/tasks/data_traversal.rb
163
+ - lib/i18n/tasks/fill_tasks.rb
164
+ - lib/i18n/tasks/google_translation.rb
145
165
  - lib/i18n/tasks/ignore_keys.rb
146
166
  - lib/i18n/tasks/key_pattern_matching.rb
147
167
  - lib/i18n/tasks/missing_keys.rb
148
- - lib/i18n/tasks/output/terminal.rb
149
168
  - lib/i18n/tasks/plural_keys.rb
150
169
  - lib/i18n/tasks/railtie.rb
151
170
  - lib/i18n/tasks/relative_keys.rb
171
+ - lib/i18n/tasks/reports/terminal.rb
152
172
  - lib/i18n/tasks/source_keys.rb
153
- - lib/i18n/tasks/translation.rb
154
173
  - lib/i18n/tasks/translation_data.rb
155
174
  - lib/i18n/tasks/untranslated_keys.rb
156
175
  - lib/i18n/tasks/unused_keys.rb
@@ -192,7 +211,7 @@ rubyforge_project:
192
211
  rubygems_version: 2.1.10
193
212
  signing_key:
194
213
  specification_version: 4
195
- summary: Find all the missing and unused translations!
214
+ summary: Tasks to manage missing and unused translations in Rails.
196
215
  test_files:
197
216
  - spec/fixtures/app/assets/javascripts/application.js
198
217
  - spec/fixtures/app/controllers/events_controller.rb
@@ -1,67 +0,0 @@
1
- # coding: utf-8
2
- module I18n
3
- module Tasks
4
- module Output
5
- class Terminal
6
- include Term::ANSIColor
7
-
8
- def missing(missing)
9
- print_title "Missing keys and translations (#{missing.length})"
10
- if missing.present?
11
- $stderr.puts "#{bold 'Legend:'} #{red '✗'} key missing, #{yellow bold '∅'} translation blank, #{blue bold '='} value equal to base locale, #{cyan 'value in base locale'}"
12
- key_col_width = missing.map { |x| x[:key] }.max_by(&:length).length + 2
13
- missing.each { |m| print_missing_translation m, key_col_width: key_col_width }
14
- else
15
- print_success 'Good job! No translations missing!'
16
- end
17
- end
18
-
19
- def unused(unused)
20
- print_title "Unused i18n keys (#{unused.length})"
21
- if unused.present?
22
- key_col_width = unused.max_by { |x| x[0].length }[0].length + 2
23
- unused.each { |(key, value)| puts "#{magenta key.ljust(key_col_width)}#{cyan value.to_s.strip}" }
24
- else
25
- print_success 'Good job! Every translation is used!'
26
- end
27
- end
28
-
29
- private
30
-
31
- extend Term::ANSIColor
32
-
33
- def print_title(title)
34
- $stderr.puts "#{bold cyan title.strip} #{dark "|"} #{bold "i18n-tasks v#{I18n::Tasks::VERSION}"}"
35
- end
36
-
37
- def print_success(message)
38
- $stderr.puts(bold green message)
39
- end
40
-
41
-
42
- def print_missing_translation(m, opts)
43
- status_texts = {
44
- none: red("✗".ljust(6)),
45
- blank: yellow(bold '∅'.ljust(6)),
46
- eq_base: blue(bold "=".ljust(6))
47
- }
48
- locale, key, base_value, status_text = m[:locale], m[:key], m[:base_value].to_s.try(:strip), " #{status_texts[m[:type]]}"
49
-
50
- key = magenta "#{key}".ljust(opts[:key_col_width])
51
- s = if m[:type] == :none
52
- "#{red bold locale.ljust(4)} #{status_text} #{key}"
53
- else
54
- "#{bold locale.ljust(4)} #{status_text} #{key} #{cyan base_value.strip.gsub("\n", ' ')}"
55
- end
56
- puts s
57
- end
58
-
59
- private
60
- def indent(txt, n = 2)
61
- spaces = ' ' * n
62
- txt.gsub /^/, spaces
63
- end
64
- end
65
- end
66
- end
67
- end