i18n-tasks 0.3.9 → 0.3.10
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGES.md +5 -0
- data/Gemfile +1 -0
- data/README.md +23 -16
- data/bin/i18n-tasks +21 -14
- data/lib/i18n/tasks/commands.rb +17 -8
- data/lib/i18n/tasks/data/adapter/json_adapter.rb +4 -4
- data/lib/i18n/tasks/data/adapter/yaml_adapter.rb +4 -4
- data/lib/i18n/tasks/data/file_formats.rb +11 -9
- data/lib/i18n/tasks/data/file_system.rb +2 -2
- data/lib/i18n/tasks/version.rb +1 -1
- data/spec/fixtures/app/views/index.html.slim +1 -1
- data/spec/fixtures/config/i18n-tasks.yml +3 -0
- data/spec/i18n_tasks_spec.rb +23 -3
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 601f3e2a8ac21ec9d8f83aa71f5723623b109f69
|
4
|
+
data.tar.gz: 5a9396e63a232357fd63d1d260a80ddfd274dea4
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 80187dee2d375fbe01e79313c6d7bd24927c91da51c52b102661f7b13378547c16743b571c0ebdb7fd3d29d35777db48900ce66f5d23108069d6f06f0510ac7b
|
7
|
+
data.tar.gz: fe57c3b82c6d24c3d43c3203a184b21d99a04ec639d142aee38b1f336587d74308937ea9a7a33ac8a8a9b1d16296e83bf58e46749bbbbfb453a715cf0087924e
|
data/CHANGES.md
CHANGED
@@ -2,6 +2,11 @@
|
|
2
2
|
|
3
3
|
* Fix regression: Remove ActiveSupport::HashWithIndifferentAccess from locale data output
|
4
4
|
|
5
|
+
## 0.3.10
|
6
|
+
|
7
|
+
* New (de)serialization options in config
|
8
|
+
* `add-missing` placeholder argument can now use %{base_value}.
|
9
|
+
|
5
10
|
## 0.3.8
|
6
11
|
|
7
12
|
* Fix activesupport ~3.x compatibility issue (#45).
|
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
# i18n-tasks [![Build Status][badge-travis]][travis] [![Coverage Status][badge-coveralls]][coveralls] [![Code Climate][badge-code-climate]][code-climate] [![
|
1
|
+
# i18n-tasks [![Build Status][badge-travis]][travis] [![Coverage Status][badge-coveralls]][coveralls] [![Code Climate][badge-code-climate]][code-climate] [![Gemnasium][badge-gemnasium]][gemnasium]
|
2
2
|
|
3
3
|
i18n-tasks finds and manages missing and unused translations in your application.
|
4
4
|
|
@@ -7,7 +7,7 @@ If you use a key that does not exist, this will only blow up at runtime. Keys le
|
|
7
7
|
in the resource files and introduce unnecessary overhead on the translators. Translation files can quickly turn to disarray.
|
8
8
|
|
9
9
|
i18n-tasks improves this by using static analysis. It scans calls such as `I18n.t('some.key')` and provides reports on key usage, missing, and unused keys.
|
10
|
-
It can also
|
10
|
+
It can also pre-fill missing keys, including from Google Translate, and it can remove unused keys as well.
|
11
11
|
|
12
12
|
i18n-tasks can be used with any project using [i18n][i18n-gem] (default in Rails), or similar, even if it isn't ruby.
|
13
13
|
|
@@ -18,7 +18,7 @@ i18n-tasks can be used with any project using [i18n][i18n-gem] (default in Rails
|
|
18
18
|
Add to Gemfile:
|
19
19
|
|
20
20
|
```ruby
|
21
|
-
gem 'i18n-tasks', '~> 0.3.
|
21
|
+
gem 'i18n-tasks', '~> 0.3.10'
|
22
22
|
```
|
23
23
|
|
24
24
|
i18n-tasks does not load or execute any of the application's code but performs static-only analysic.
|
@@ -55,6 +55,8 @@ To add missing values to the base locale only:
|
|
55
55
|
```bash
|
56
56
|
# most task accept locales as first argument. `base` and `all` are special
|
57
57
|
i18n-tasks add-missing base
|
58
|
+
# add-missing accepts a placeholder argument, with optional base_value interpolation
|
59
|
+
i18n-tasks add-missing -p 'PLEASE-TRANSLATE %{base_value}' fr
|
58
60
|
```
|
59
61
|
|
60
62
|
Translate missing values with Google Translate ([more below on the API key](#translation-config)).
|
@@ -142,6 +144,11 @@ data:
|
|
142
144
|
- ['devise.*', 'config/locales/devise.%{locale}.yml']
|
143
145
|
# default catch-all:
|
144
146
|
- 'config/locales/%{locale}.yml' # path is short for ['*', path]
|
147
|
+
# configure YAML / JSON serializer options (when using the default adapter)
|
148
|
+
yaml:
|
149
|
+
write:
|
150
|
+
# do not wrap lines at 80 characters (default)
|
151
|
+
line_width: -1
|
145
152
|
```
|
146
153
|
|
147
154
|
#### Key pattern syntax
|
@@ -254,23 +261,23 @@ You might want to test for missing and unused translations as part of your test
|
|
254
261
|
This is how you can do it with rspec:
|
255
262
|
|
256
263
|
```ruby
|
257
|
-
# spec/
|
264
|
+
# spec/i18n_spec.rb:
|
258
265
|
require 'spec_helper'
|
259
|
-
|
260
266
|
require 'i18n/tasks'
|
261
267
|
|
262
|
-
describe '
|
268
|
+
describe 'I18n' do
|
263
269
|
let(:i18n) { I18n::Tasks::BaseTask.new }
|
264
270
|
|
265
|
-
it '
|
266
|
-
|
271
|
+
it 'does not have missing keys' do
|
272
|
+
count = i18n.missing_keys.count
|
273
|
+
fail "There are #{count} missing i18n keys! Run 'i18n-tasks missing' for more details." unless count.zero?
|
267
274
|
end
|
268
275
|
|
269
|
-
it '
|
270
|
-
|
276
|
+
it 'does not have unused keys' do
|
277
|
+
count = i18n.unused_keys.count
|
278
|
+
fail "There are #{count} unused i18n keys! Run 'i18n-tasks unused' for more details." unless count.zero?
|
271
279
|
end
|
272
280
|
end
|
273
|
-
|
274
281
|
```
|
275
282
|
|
276
283
|
## XLSX
|
@@ -292,12 +299,12 @@ This was originally developed for [Zuigo](http://zuigo.com/), a platform to orga
|
|
292
299
|
|
293
300
|
[MIT license]: /LICENSE.txt
|
294
301
|
[travis]: https://travis-ci.org/glebm/i18n-tasks
|
295
|
-
[badge-travis]:
|
302
|
+
[badge-travis]: http://img.shields.io/travis/glebm/i18n-tasks.svg
|
296
303
|
[coveralls]: https://coveralls.io/r/glebm/i18n-tasks?branch=master
|
297
|
-
[badge-coveralls]:
|
304
|
+
[badge-coveralls]: http://img.shields.io/coveralls/glebm/i18n-tasks.svg
|
305
|
+
[gemnasium]: https://gemnasium.com/glebm/i18n-tasks
|
306
|
+
[badge-gemnasium]: https://gemnasium.com/glebm/i18n-tasks.svg
|
298
307
|
[code-climate]: https://codeclimate.com/github/glebm/i18n-tasks
|
299
|
-
[badge-code-climate]:
|
300
|
-
[badge-flattr]: https://api.flattr.com/button/flattr-badge-large.png
|
301
|
-
[flattr]: https://flattr.com/submit/auto?user_id=glebm&url=https%3A%2F%2Fgithub.com%2Fglebm%2Fi18n-tasks
|
308
|
+
[badge-code-climate]: http://img.shields.io/codeclimate/github/glebm/i18n-tasks.svg
|
302
309
|
[i18n-gem]: https://github.com/svenfuchs/i18n "svenfuchs/i18n on Github"
|
303
310
|
[screenshot-find]: https://raw.github.com/glebm/i18n-tasks/master/doc/img/i18n-usages.png "i18n-tasks find output screenshot"
|
data/bin/i18n-tasks
CHANGED
@@ -4,21 +4,30 @@ require 'i18n/tasks'
|
|
4
4
|
require 'i18n/tasks/commands'
|
5
5
|
require 'slop'
|
6
6
|
|
7
|
+
err = proc { |message, exit_code|
|
8
|
+
STDERR.puts Term::ANSIColor.yellow('i18n-tasks: ' + message)
|
9
|
+
exit exit_code
|
10
|
+
}
|
11
|
+
|
7
12
|
command = nil
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
13
|
+
begin
|
14
|
+
slop = Slop.parse(help: true) do
|
15
|
+
on('-v', '--version', 'Print the version') {
|
16
|
+
puts I18n::Tasks::VERSION
|
17
|
+
exit
|
18
|
+
}
|
19
|
+
::I18n::Tasks::Commands.cmds.each do |name, attr|
|
20
|
+
command name.tr('_', '-') do
|
21
|
+
description attr.desc if attr.desc
|
22
|
+
instance_exec(&attr.opts) if attr.opts
|
23
|
+
run do |opts, args|
|
24
|
+
command = [name, opts, args]
|
25
|
+
end
|
19
26
|
end
|
20
27
|
end
|
21
28
|
end
|
29
|
+
rescue Slop::Error => e
|
30
|
+
err.call(e.message, 64)
|
22
31
|
end
|
23
32
|
|
24
33
|
if command
|
@@ -40,7 +49,5 @@ if command
|
|
40
49
|
# i18n-tasks missing | head
|
41
50
|
end
|
42
51
|
else
|
43
|
-
|
44
|
-
puts slop.help
|
45
|
-
exit 64
|
52
|
+
err.call("Command unknown: #{ARGV[0]}", 64) if ARGV[0]
|
46
53
|
end
|
data/lib/i18n/tasks/commands.rb
CHANGED
@@ -11,7 +11,7 @@ module I18n::Tasks
|
|
11
11
|
desc 'show missing translations'
|
12
12
|
opts do
|
13
13
|
on '-l', :locales=, 'Filter by locale (default: all)', on_locale_opt
|
14
|
-
on '-t', :types
|
14
|
+
on '-t', :types=, 'Filter by type (types: missing_from_base, eq_base, missing_from_locale)', as: Array, delimiter: /[+:,]/
|
15
15
|
end
|
16
16
|
cmd :missing do |opt = {}|
|
17
17
|
parse_locales! opt
|
@@ -25,8 +25,8 @@ module I18n::Tasks
|
|
25
25
|
|
26
26
|
desc 'translate missing keys with Google Translate'
|
27
27
|
opts do
|
28
|
-
on '-l', :locales
|
29
|
-
on '-f', :from
|
28
|
+
on '-l', :locales=, 'Locales to translate (default: all)', on_locale_opt
|
29
|
+
on '-f', :from=, 'Locale to translate from (default: base)', default: 'base', argument: true, optional: false
|
30
30
|
end
|
31
31
|
cmd :translate_missing do |opt = {}|
|
32
32
|
opt[:from] = base_locale if opt[:from].blank? || opt[:from] == 'base'
|
@@ -36,21 +36,30 @@ module I18n::Tasks
|
|
36
36
|
|
37
37
|
desc 'add missing keys to the locales'
|
38
38
|
opts do
|
39
|
-
on '-l', :locales
|
40
|
-
on '-p', :placeholder
|
39
|
+
on '-l', :locales=, 'Locales to add keys into (default: all)', on_locale_opt
|
40
|
+
on '-p', :placeholder=, 'Value for empty keys (default: base value or key.humanize)', argument: true, optional: false
|
41
41
|
end
|
42
42
|
cmd :add_missing do |opt = {}|
|
43
43
|
parse_locales! opt
|
44
44
|
opt[:value] ||= opt.delete(:placeholder) || proc { |key, locale|
|
45
45
|
# default to base value or key.humanize
|
46
|
-
locale
|
46
|
+
locale != base_locale && t(key, base_locale) || key.split('.').last.to_s.humanize
|
47
47
|
}
|
48
|
+
|
49
|
+
v = opt[:value]
|
50
|
+
if v.is_a?(String) && v.include?('%{base_value}')
|
51
|
+
opt[:value] = proc { |key, locale|
|
52
|
+
base_value = t(key, base_locale) || ''
|
53
|
+
v % {base_value: base_value}
|
54
|
+
}
|
55
|
+
end
|
56
|
+
|
48
57
|
i18n_task.fill_missing_value opt
|
49
58
|
end
|
50
59
|
|
51
60
|
desc 'show where the keys are used in the code'
|
52
61
|
opts do
|
53
|
-
on '-p', :pattern
|
62
|
+
on '-p', :pattern=, 'Show only keys matching pattern', argument: true, optional: false
|
54
63
|
end
|
55
64
|
cmd :find do |opt = {}|
|
56
65
|
opt[:filter] ||= opt.delete(:pattern) || opt[:arguments].try(:first)
|
@@ -95,7 +104,7 @@ module I18n::Tasks
|
|
95
104
|
|
96
105
|
desc 'save missing and unused translations to an Excel file'
|
97
106
|
opts do
|
98
|
-
on :path
|
107
|
+
on :path=, 'Destination path', default: 'tmp/i18n-report.xlsx'
|
99
108
|
end
|
100
109
|
cmd :xlsx_report do |opt = {}|
|
101
110
|
begin
|
@@ -7,13 +7,13 @@ module I18n::Tasks
|
|
7
7
|
extend self
|
8
8
|
|
9
9
|
# @return [Hash] locale tree
|
10
|
-
def parse(str)
|
11
|
-
JSON.parse(str)
|
10
|
+
def parse(str, opts)
|
11
|
+
JSON.parse(str, opts || {})
|
12
12
|
end
|
13
13
|
|
14
14
|
# @return [String]
|
15
|
-
def dump(tree)
|
16
|
-
JSON.generate(tree)
|
15
|
+
def dump(tree, opts)
|
16
|
+
JSON.generate(tree, opts || {})
|
17
17
|
end
|
18
18
|
|
19
19
|
end
|
@@ -6,13 +6,13 @@ module I18n::Tasks
|
|
6
6
|
extend self
|
7
7
|
|
8
8
|
# @return [Hash] locale tree
|
9
|
-
def parse(str)
|
10
|
-
YAML.load(str)
|
9
|
+
def parse(str, options)
|
10
|
+
YAML.load(str, options || {})
|
11
11
|
end
|
12
12
|
|
13
13
|
# @return [String]
|
14
|
-
def dump(tree)
|
15
|
-
tree.to_yaml
|
14
|
+
def dump(tree, options)
|
15
|
+
tree.to_yaml(options || {})
|
16
16
|
end
|
17
17
|
|
18
18
|
end
|
@@ -12,29 +12,31 @@ module I18n
|
|
12
12
|
|
13
13
|
protected
|
14
14
|
|
15
|
-
def load_file(
|
16
|
-
adapter_for(
|
17
|
-
|
18
|
-
)
|
15
|
+
def load_file(path)
|
16
|
+
adapter_name, adapter_pattern, adapter = adapter_for(path)
|
17
|
+
adapter_options = (config[adapter_name] || {})[:read]
|
18
|
+
adapter.parse(::File.read(path), adapter_options)
|
19
19
|
end
|
20
20
|
|
21
21
|
def write_tree(path, tree)
|
22
22
|
::File.open(path, 'w') { |f|
|
23
|
-
|
23
|
+
adapter_name, adapter_pattern, adapter = adapter_for(path)
|
24
|
+
adapter_options = (config[adapter_name] || {})[:write]
|
25
|
+
f.write(adapter.dump(tree.to_hash, adapter_options))
|
24
26
|
}
|
25
27
|
end
|
26
28
|
|
27
29
|
module ClassMethods
|
28
30
|
# @param pattern [String] File.fnmatch pattern
|
29
31
|
# @param adapter [responds to parse(string)->hash and dump(hash)->string]
|
30
|
-
def register_adapter(pattern, adapter)
|
31
|
-
(@fn_patterns ||=
|
32
|
+
def register_adapter(name, pattern, adapter)
|
33
|
+
(@fn_patterns ||= []) << [name, pattern, adapter]
|
32
34
|
end
|
33
35
|
|
34
36
|
def adapter_for(path)
|
35
|
-
@fn_patterns.detect { |pattern, adapter|
|
37
|
+
@fn_patterns.detect { |(name, pattern, adapter)|
|
36
38
|
::File.fnmatch(pattern, path)
|
37
|
-
}
|
39
|
+
} or raise "Adapter not found for #{path}. Registered adapters: #{@fn_patterns.inspect}"
|
38
40
|
end
|
39
41
|
end
|
40
42
|
end
|
@@ -5,8 +5,8 @@ require 'i18n/tasks/data/adapter/yaml_adapter'
|
|
5
5
|
module I18n::Tasks
|
6
6
|
module Data
|
7
7
|
class FileSystem < FileSystemBase
|
8
|
-
register_adapter '*.yml', Adapter::YamlAdapter
|
9
|
-
register_adapter '*.json', Adapter::JsonAdapter
|
8
|
+
register_adapter :yaml, '*.yml', Adapter::YamlAdapter
|
9
|
+
register_adapter :json, '*.json', Adapter::JsonAdapter
|
10
10
|
end
|
11
11
|
end
|
12
12
|
end
|
data/lib/i18n/tasks/version.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
p #{t('ca.a')} #{t 'ca.b'} #{t "ca.c"}
|
2
2
|
p #{t 'ca.d'} #{t 'ca.f', i: 'world'} #{t 'ca.e', i: 'world'}
|
3
3
|
p #{t 'missing_in_es.a'} #{t 'same_in_es.a'} #{t 'blank_in_es.a'}
|
4
|
-
p = t 'used_but_missing.
|
4
|
+
p = t 'used_but_missing.key'
|
5
5
|
p = t 'x', scope: 'scoped'
|
6
6
|
p = t 'x', scope: [:very, :scoped]
|
7
7
|
p = t 'x', scope: [:scoped, code]
|
data/spec/i18n_tasks_spec.rb
CHANGED
@@ -8,7 +8,7 @@ describe 'i18n-tasks' do
|
|
8
8
|
|
9
9
|
describe 'missing' do
|
10
10
|
let (:expected_missing_keys) {
|
11
|
-
%w( en.used_but_missing.
|
11
|
+
%w( en.used_but_missing.key en.relative.index.missing
|
12
12
|
es.missing_in_es.a es.blank_in_es.a es.same_in_es.a
|
13
13
|
en.hash.pattern_missing.a en.hash.pattern_missing.b
|
14
14
|
en.missing_symbol_key en.missing_symbol.key_two en.missing_symbol.key_three )
|
@@ -76,13 +76,23 @@ describe 'i18n-tasks' do
|
|
76
76
|
end
|
77
77
|
|
78
78
|
describe 'add_missing' do
|
79
|
-
it 'default placeholder' do
|
79
|
+
it 'default placeholder: key.humanize for base_locale' do
|
80
80
|
in_test_app_dir {
|
81
81
|
expect(YAML.load_file('config/locales/en.yml')['en']['used_but_missing']).to be_nil
|
82
82
|
}
|
83
83
|
run_cmd :add_missing, locales: 'base'
|
84
84
|
in_test_app_dir {
|
85
|
-
expect(YAML.load_file('config/locales/en.yml')['en']['used_but_missing']['
|
85
|
+
expect(YAML.load_file('config/locales/en.yml')['en']['used_but_missing']['key']).to eq 'Key'
|
86
|
+
}
|
87
|
+
end
|
88
|
+
|
89
|
+
it 'default placeholder: base_value for non-base locale' do
|
90
|
+
in_test_app_dir {
|
91
|
+
expect(YAML.load_file('config/locales/es.yml')['es']['missing_in_es']).to be_nil
|
92
|
+
}
|
93
|
+
run_cmd :add_missing, locales: 'es'
|
94
|
+
in_test_app_dir {
|
95
|
+
expect(YAML.load_file('config/locales/es.yml')['es']['missing_in_es']['a']).to eq 'EN_TEXT'
|
86
96
|
}
|
87
97
|
end
|
88
98
|
|
@@ -97,6 +107,16 @@ describe 'i18n-tasks' do
|
|
97
107
|
expect(YAML.load_file('config/locales/devise.es.yml')['es']['devise']['a']).to eq 'ES_TEXT'
|
98
108
|
}
|
99
109
|
end
|
110
|
+
|
111
|
+
it 'placeholder: value with base_value' do
|
112
|
+
in_test_app_dir {
|
113
|
+
expect(YAML.load_file('config/locales/es.yml')['es']['missing_in_es']).to be_nil
|
114
|
+
}
|
115
|
+
run_cmd :add_missing, locales: 'all', placeholder: 'TRME %{base_value}'
|
116
|
+
in_test_app_dir {
|
117
|
+
expect(YAML.load_file('config/locales/es.yml')['es']['missing_in_es']['a']).to eq 'TRME EN_TEXT'
|
118
|
+
}
|
119
|
+
end
|
100
120
|
end
|
101
121
|
|
102
122
|
describe 'config' do
|
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.3.
|
4
|
+
version: 0.3.10
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- glebm
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2014-
|
11
|
+
date: 2014-04-13 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: erubis
|