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 +4 -4
- data/CHANGES.md +8 -0
- data/README.md +4 -4
- data/doc/img/i18n-tasks.gif +0 -0
- data/i18n-tasks.gemspec +6 -2
- data/lib/i18n/tasks.rb +1 -0
- data/lib/i18n/tasks/base_task.rb +8 -14
- data/lib/i18n/tasks/configuration.rb +38 -0
- data/lib/i18n/tasks/data_traversal.rb +7 -2
- data/lib/i18n/tasks/fill_tasks.rb +54 -0
- data/lib/i18n/tasks/{translation.rb → google_translation.rb} +1 -9
- data/lib/i18n/tasks/ignore_keys.rb +3 -2
- data/lib/i18n/tasks/key_pattern_matching.rb +1 -1
- data/lib/i18n/tasks/missing_keys.rb +13 -23
- data/lib/i18n/tasks/plural_keys.rb +5 -5
- data/lib/i18n/tasks/railtie.rb +1 -1
- data/lib/i18n/tasks/reports/terminal.rb +86 -0
- data/lib/i18n/tasks/source_keys.rb +0 -20
- data/lib/i18n/tasks/translation_data.rb +11 -25
- data/lib/i18n/tasks/untranslated_keys.rb +15 -2
- data/lib/i18n/tasks/unused_keys.rb +5 -8
- data/lib/i18n/tasks/version.rb +1 -1
- data/lib/tasks/i18n-tasks.rake +23 -46
- data/spec/i18n_tasks_spec.rb +2 -1
- data/spec/support/i18n_tasks_output_matcher.rb +5 -2
- metadata +25 -6
- data/lib/i18n/tasks/output/terminal.rb +0 -67
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 938b362fb2bb4551507b503b2230e7175eff7a97
|
4
|
+
data.tar.gz: 378887ed86ebbf0cffc511d4a5b6ad83627d690f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 182391f6acbb385bf294cf675debfef292153309bec9beacda7b5f8d6e71b178e21d2a111d7bdf31aec843bc1ec9d7fac53fd1a2a59a098132e887c7653f9de0
|
7
|
+
data.tar.gz: 3f0701378d7ecea4be0f252feae1a59a3ca9f89beeb5f6e14cd0a0f9f8ca6d807bb13ced370425d612c23bfb7c76627eef5509068cf6fc2267e699596cbae2a9
|
data/CHANGES.md
CHANGED
data/README.md
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
# i18n-tasks [](https://travis-ci.org/glebm/i18n-tasks) [](https://codeclimate.com/github/glebm/i18n-tasks)
|
2
2
|
|
3
3
|
|
4
|
-
|
4
|
+
Tasks to manage missing and unused translations in Rails.
|
5
5
|
|
6
6
|

|
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.
|
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
|
121
|
-
pattern: "\\bt[( ]\\s*(
|
120
|
+
# you can override the default key regex pattern:
|
121
|
+
pattern: "\\bt[( ]\\s*(:?\".+?\"|:?'.+?'|:\\w+)"
|
122
122
|
```
|
123
123
|
|
124
124
|
### Fine-tuning
|
data/doc/img/i18n-tasks.gif
CHANGED
Binary file
|
data/i18n-tasks.gemspec
CHANGED
@@ -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{
|
12
|
-
spec.description = %q{
|
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'
|
data/lib/i18n/tasks.rb
CHANGED
data/lib/i18n/tasks/base_task.rb
CHANGED
@@ -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
|
25
|
-
include
|
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 =>
|
9
|
-
def
|
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::
|
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
|
3
|
-
# will apply global ignore rules
|
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
|
-
|
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
|
-
}
|
6
|
+
}
|
8
7
|
end
|
9
8
|
|
10
|
-
#
|
11
|
-
|
12
|
-
|
13
|
-
|
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
|
-
#
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
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
|
5
|
-
# @param
|
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(
|
8
|
-
return key if key !~ PLURAL_KEY_RE || t(
|
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(
|
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
|
data/lib/i18n/tasks/railtie.rb
CHANGED
@@ -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
|
-
|
30
|
-
|
31
|
-
|
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!(
|
36
|
-
|
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
|
-
|
10
|
-
|
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
|
-
|
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(
|
13
|
-
|
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
|
data/lib/i18n/tasks/version.rb
CHANGED
data/lib/tasks/i18n-tasks.rake
CHANGED
@@ -1,95 +1,72 @@
|
|
1
1
|
require 'set'
|
2
2
|
require 'i18n/tasks/base_task'
|
3
|
-
require 'i18n/tasks/
|
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
|
-
|
9
|
+
report.missing_translations
|
9
10
|
end
|
10
11
|
|
11
12
|
desc 'show unused translations'
|
12
13
|
task :unused => 'i18n:setup' do
|
13
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
65
|
-
|
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
|
-
|
71
|
-
|
72
|
-
included do
|
73
|
-
delegate :t, :locales, :base_locale, :normalize_store!, :fill_blanks!, to: :i18n_tasks
|
54
|
+
include Term::ANSIColor
|
74
55
|
|
75
|
-
|
76
|
-
@i18n_tasks ||= I18n::Tasks::BaseTask.new
|
77
|
-
end
|
56
|
+
delegate :base_locale, to: :i18n_tasks
|
78
57
|
|
79
|
-
|
80
|
-
|
81
|
-
|
58
|
+
def i18n_tasks
|
59
|
+
@i18n_tasks ||= I18n::Tasks::BaseTask.new
|
60
|
+
end
|
82
61
|
|
83
|
-
|
84
|
-
|
85
|
-
|
62
|
+
def report
|
63
|
+
@report ||= I18n::Tasks::Reports::Terminal.new
|
64
|
+
end
|
86
65
|
|
87
|
-
|
88
|
-
|
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
|
+
|
data/spec/i18n_tasks_spec.rb
CHANGED
@@ -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')
|
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
|
9
|
-
|
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.
|
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-
|
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:
|
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:
|
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
|