i18n-tasks 0.2.1 → 0.2.2

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