i18n-tasks 0.3.11 → 0.4.0.beta1

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: 84ba1f92ba2da94773501e168515c76c3c6c21ab
4
- data.tar.gz: 5d72ea2de6df682ab6f57b8f76d051f6d8b0f3e8
3
+ metadata.gz: 328ef834a8ef2756aa154f9e4eb5207df02486d4
4
+ data.tar.gz: d3d0d63e4fb566037f64606cdfd2a140928492c1
5
5
  SHA512:
6
- metadata.gz: 46efbb3c38b02738ee34069bc4d9384603485f5f0aeba4227139596ce7bd383cb5c9b9b2f98ddadacd9c1c28bec47170e137f02856d2b4de56a7d724ab853a76
7
- data.tar.gz: a8b7a4968b32960205152674040c913e4032d999ee90f2b48e789593c1f81cfef3264c5f747d34608246548dd9fbbaafb94e7b01f8b825849515f7cf802956b7
6
+ metadata.gz: 0ea465b2b3799df29d77c5db1c7fdbb5deb824b838399f663ef82411f62dee84f14fef9e34498b958e06e53deacd91bbf53252933f9792ef4ad2f0cd3b2c0a5f
7
+ data.tar.gz: f0e8d5a760021dd976741f28f06bc5aa2e4fcdc2158573b2eeb9795f8984f6c15c39a46a717f7de44a4bcd8757e858541b74a08230c9433534eb148922d831b2
data/.jrubyrc ADDED
@@ -0,0 +1,2 @@
1
+ compat.version=2.0
2
+
data/.travis.yml CHANGED
@@ -1,10 +1,13 @@
1
1
  language: ruby
2
2
  rvm:
3
- - 2.1.0
3
+ - 2.1.1
4
4
  - 2.0.0
5
- - 1.9.3
6
5
  - jruby
7
6
  - rbx
7
+ matrix:
8
+ allow_failures:
9
+ # no keyword argument support in rbx
10
+ - rbx
8
11
  env:
9
12
  global:
10
13
  - TRAVIS=1
data/CHANGES.md CHANGED
@@ -1,3 +1,11 @@
1
+ ## 0.4.0 (not yet released)
2
+
3
+ * In addition to pattern router, a new conservative router that keeps the keys in place. (See [#57](https://github.com/glebm/i18n-tasks/issues/57))
4
+ * `i18n-tasks irb` for debugging
5
+ * This release is a major refactoring to use real trees internally (as opposed to nested hashes).
6
+ Real trees allow for much easier [traversal](/lib/i18n/tasks/data/tree/traversal.rb).
7
+ With these trees, information can be associated with each node, which allows for things like the conservative router.
8
+
1
9
  ## 0.3.11
2
10
 
3
11
  * Improve plural key handling
data/Gemfile CHANGED
@@ -3,15 +3,7 @@ source 'https://rubygems.org'
3
3
  # Specify your gem's dependencies in i18n-tasks.gemspec
4
4
  gemspec
5
5
 
6
- platform :rbx do
7
- gem 'rubysl', '~> 2.0'
8
- gem 'rubysl-mutex_m', '~> 2.0'
9
- gem 'psych'
10
- group :development do
11
- gem 'racc'
12
- end
13
- end
14
-
15
6
  group :development do
16
7
  gem 'coveralls', require: false
8
+ gem 'byebug', platform: :mri, require: false
17
9
  end
data/README.md CHANGED
@@ -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.10'
21
+ gem 'i18n-tasks', '~> 0.3.11'
22
22
  ```
23
23
 
24
24
  i18n-tasks does not load or execute any of the application's code but performs static-only analysic.
@@ -45,6 +45,7 @@ Available commands:
45
45
  remove-unused remove unused keys
46
46
  config display i18n-tasks configuration
47
47
  xlsx-report save missing and unused translations to an Excel file
48
+ irb irb session within i18n-tasks context
48
49
 
49
50
  See `<command> --help` for more information on a specific command.
50
51
  ```
@@ -90,6 +91,13 @@ i18n-tasks find '{number,currency}.format.*'
90
91
 
91
92
  ![i18n-screenshot][screenshot-find]
92
93
 
94
+ ### Find / remove unused keys
95
+
96
+ ```bash
97
+ i18n-tasks unused
98
+ i18n-tasks remove-unused
99
+ ```
100
+
93
101
  #### Features
94
102
 
95
103
  Relative keys (`t '.title'`) and plural keys (`key.{one,many,other,...}`) are fully supported.
@@ -144,12 +152,13 @@ data:
144
152
 
145
153
  #### Multiple locale files
146
154
 
147
- Use `data.read` and `data.write` options to work with locale data spread over multiple files.
155
+ Use `data` options to work with locale data spread over multiple files.
156
+
157
+ `data.read` accepts a list of file globs to read from per-locale:
148
158
 
149
159
  ```
150
160
  # config/i18n-tasks.yml
151
161
  data:
152
- # a list of file globs to read from per-locale
153
162
  read:
154
163
  # read from namespaced files, e.g. simple_form.en.yml
155
164
  - 'config/locales/*.%{locale}.yml'
@@ -157,6 +166,18 @@ data:
157
166
  - "<%= %x[bundle show vagrant].chomp %>/templates/locales/%{locale}.yml"
158
167
  # default
159
168
  - 'config/locales/%{locale}.yml'
169
+ ```
170
+
171
+ For writing to locale files i18n-tasks provides 2 options.
172
+
173
+ ##### Pattern router
174
+
175
+ Pattern router organizes keys based on a list of key patterns, as in the example below:
176
+
177
+ ```
178
+ data:
179
+ # pattern_router is default
180
+ router: pattern_router
160
181
  # a list of {key pattern => file} routes, matched top to bottom
161
182
  write:
162
183
  # write models.* and views.* keys to the respective files
@@ -167,6 +188,18 @@ data:
167
188
  - 'config/locales/%{locale}.yml'
168
189
  ```
169
190
 
191
+ ##### Conservative router (v0.4.0+)
192
+
193
+ Conservative router keeps the keys where they are found, or infers the path from base locale.
194
+ If the key is completely new, conservative router will fall back to the pattern router behaviour.
195
+
196
+ ```
197
+ data:
198
+ router: conservative_router
199
+ write:
200
+ - 'config/locales/%{locale}.yml'
201
+ ```
202
+
170
203
  #### Key pattern syntax
171
204
 
172
205
  | syntax | description |
data/Rakefile CHANGED
@@ -3,3 +3,9 @@ require 'rspec/core/rake_task'
3
3
  RSpec::Core::RakeTask.new(:spec)
4
4
  task :default => :spec
5
5
 
6
+ task :irb do
7
+ # $: << File.expand_path('lib', __FILE__)
8
+ require 'i18n/tasks'
9
+ require 'i18n/tasks/commands'
10
+ ::I18n::Tasks::Commands.new.irb
11
+ end
data/bin/i18n-tasks CHANGED
@@ -11,7 +11,9 @@ err = proc { |message, exit_code|
11
11
 
12
12
  command = nil
13
13
  begin
14
- slop = Slop.parse(help: true) do
14
+ args = ARGV.dup
15
+ args = ['--help'] if args.empty?
16
+ slop = Slop.parse(args, help: true) do
15
17
  on('-v', '--version', 'Print the version') {
16
18
  puts I18n::Tasks::VERSION
17
19
  exit
@@ -0,0 +1,4 @@
1
+ ---
2
+ en:
3
+ y: 3
4
+ z: 4
@@ -0,0 +1 @@
1
+ --- {}
data/i18n-tasks.gemspec CHANGED
@@ -29,11 +29,11 @@ It can also can pre-fill missing keys, including from Google Translate, and it c
29
29
 
30
30
  s.add_dependency 'erubis'
31
31
  s.add_dependency 'activesupport'
32
- s.add_dependency 'easy_translate', '>= 0.4.0'
32
+ s.add_dependency 'easy_translate', '>= 0.5.0'
33
33
  s.add_dependency 'term-ansicolor'
34
34
  s.add_dependency 'terminal-table'
35
35
  s.add_dependency 'highline'
36
- s.add_dependency 'slop', '>= 3.4.7'
36
+ s.add_dependency 'slop', '>= 3.5.0'
37
37
  s.add_development_dependency 'axlsx', '~> 2.0'
38
38
  s.add_development_dependency 'bundler', '~> 1.3'
39
39
  s.add_development_dependency 'rake'
data/lib/i18n/tasks.rb CHANGED
@@ -1,3 +1,12 @@
1
+ # define all the modules to be able to use ::
2
+ module I18n
3
+ module Tasks
4
+ module Data
5
+ end
6
+ end
7
+ end
8
+
9
+
1
10
  require 'active_support/core_ext/hash'
2
11
  require 'active_support/core_ext/string'
3
12
  require 'active_support/core_ext/module/delegation'
@@ -11,8 +20,3 @@ require 'i18n/tasks/key'
11
20
  require 'i18n/tasks/key_group'
12
21
  require 'i18n/tasks/base_task'
13
22
 
14
- module I18n
15
- module Tasks
16
-
17
- end
18
- end
@@ -8,6 +8,7 @@ require 'i18n/tasks/missing_keys'
8
8
  require 'i18n/tasks/unused_keys'
9
9
  require 'i18n/tasks/google_translation'
10
10
  require 'i18n/tasks/fill_tasks'
11
+ require 'i18n/tasks/file_structure'
11
12
  require 'i18n/tasks/data'
12
13
  require 'i18n/tasks/configuration'
13
14
 
@@ -24,15 +25,12 @@ module I18n
24
25
  include GoogleTranslation
25
26
  include Logging
26
27
  include Configuration
28
+ include FileStructure
27
29
  include Data
28
30
 
29
31
  def initialize(config = {})
30
32
  self.config = config || {}
31
33
  end
32
-
33
- def in_task(&block)
34
- instance_exec(&block)
35
- end
36
34
  end
37
35
  end
38
36
  end
@@ -117,6 +117,15 @@ module I18n::Tasks
117
117
  spreadsheet_report.save_report opt[:path]
118
118
  end
119
119
 
120
+ desc 'irb session within i18n-tasks context'
121
+ cmd :irb do
122
+ require 'irb'
123
+ IRB.setup nil
124
+ IRB.conf[:MAIN_CONTEXT] = IRB::Irb.new.context
125
+ require 'irb/ext/multi-irb'
126
+ IRB.irb nil, i18n_task
127
+ end
128
+
120
129
  protected
121
130
 
122
131
  def terminal_report
@@ -1,78 +1,95 @@
1
1
  require 'i18n/tasks/data/file_system'
2
2
 
3
- module I18n::Tasks::Data
4
- # I18n data provider
5
- # @see I18n::Tasks::Data::FileSystem
6
- def data
7
- @data ||= begin
8
- conf = (config[:data] || {}).with_indifferent_access
9
- adapter = (conf[:adapter].presence || conf[:class].presence || :file_system).to_s
10
- if adapter !~ /[A-Z]/
11
- adapter = "I18n::Tasks::Data::#{adapter.camelize}"
3
+ module I18n::Tasks
4
+ module Data
5
+
6
+ # I18n data provider
7
+ # @see I18n::Tasks::Data::FileSystem
8
+ def data
9
+ @data ||= begin
10
+ conf = (config[:data] || {}).with_indifferent_access
11
+ adapter = (conf[:adapter].presence || conf[:class].presence || :file_system).to_s
12
+ if adapter !~ /[A-Z]/
13
+ adapter = "I18n::Tasks::Data::#{adapter.camelize}"
14
+ end
15
+ adapter.constantize.new(conf.except(:adapter, :class).merge(base_locale: base_locale))
12
16
  end
13
- adapter.constantize.new(conf.except(:adapter, :class))
14
17
  end
15
- end
16
18
 
17
- def t(key, locale = base_locale)
18
- data.t(key, locale)
19
- end
19
+ def t(key, locale = base_locale)
20
+ data.t(key, locale)
21
+ end
20
22
 
21
- def t_proc(locale = base_locale)
22
- @t_proc ||= {}
23
- @t_proc[locale] ||= proc { |key| t(key, locale) }
24
- end
23
+ def tree(locale)
24
+ data[locale][locale].children
25
+ end
25
26
 
26
- # whether the value for key exists in locale (defaults: base_locale)
27
- def key_value?(key, locale = base_locale)
28
- t(key, locale).present?
29
- end
27
+ def node(key, locale = base_locale)
28
+ data[locale]["#{locale}.#{key}"]
29
+ end
30
30
 
31
- # write to store, normalizing all data
32
- def normalize_store!(from = nil)
33
- from = self.locales unless from
34
- Array(from).each do |target_locale|
35
- # the store itself handles normalization
36
- data[target_locale] = data[target_locale]
31
+ def build_tree(hash)
32
+ I18n::Tasks::Data::Tree::Siblings.from_nested_hash(hash)
37
33
  end
38
- end
39
34
 
40
- # if :locales option present, call update_locale_data for each locale
41
- # otherwise, call update_locale_data for :locale option or base locale
42
- # @option opts [Array] :locales
43
- # @option opts [String] :locale
44
- def update_data(opts = {})
45
- if opts.key?(:locales)
46
- locales = (Array(opts[:locales]).presence || self.locales).map(&:to_s)
47
- # make sure base_locale always comes first if present
48
- locales = [base_locale] + (locales - [base_locale]) if locales.include?(base_locale)
49
- opts = opts.except(:locales)
50
- locales.each { |locale| update_locale_data(locale, opts.merge(locale: locale)) }
51
- else
52
- update_locale_data(opts[:locale] || base_locale, opts)
35
+ def t_proc(locale = base_locale)
36
+ @t_proc ||= {}
37
+ @t_proc[locale] ||= proc { |key| t(key, locale) }
53
38
  end
54
- end
55
39
 
56
- # @param locale
57
- # @option opts [Array|Proc] :keys keys to update, if proc call with locale
58
- # @option opts [String|Proc] value, if proc call with each key
59
- # @option opts [String|Proc] values, if proc call with all the keys
60
- def update_locale_data(locale, opts = {})
61
- keys = opts[:keys]
62
- keys = keys.call(locale) if keys.respond_to?(:call)
63
- return if keys.empty?
40
+ # whether the value for key exists in locale (defaults: base_locale)
41
+ def key_value?(key, locale = base_locale)
42
+ !t(key, locale).nil?
43
+ end
64
44
 
65
- values = opts[:values]
66
- values = values.call(keys, locale) if values.respond_to?(:call)
67
- values ||= begin
68
- value = opts[:value] or raise 'pass value or values'
69
- if value.respond_to?(:call)
70
- keys.map { |key| value.call(key, locale) }
45
+ # write to store, normalizing all data
46
+ def normalize_store!(from = nil)
47
+ from = self.locales unless from
48
+ Array(from).each do |target_locale|
49
+ # the store itself handles normalization
50
+ data[target_locale] = data[target_locale]
51
+ end
52
+ end
53
+
54
+ # if :locales option present, call update_locale_data for each locale
55
+ # otherwise, call update_locale_data for :locale option or base locale
56
+ # @option opts [Array] :locales
57
+ # @option opts [String] :locale
58
+ def update_data(opts = {})
59
+ if opts.key?(:locales)
60
+ locales = (Array(opts[:locales]).presence || self.locales).map(&:to_s)
61
+ # make sure base_locale always comes first if present
62
+ locales = [base_locale] + (locales - [base_locale]) if locales.include?(base_locale)
63
+ opts = opts.except(:locales)
64
+ locales.each { |locale| update_locale_data(locale, opts.merge(locale: locale)) }
71
65
  else
72
- [value] * keys.size
66
+ update_locale_data(opts[:locale] || base_locale, opts)
73
67
  end
74
68
  end
75
- data[locale] += keys.map(&:to_s).zip(values)
76
- end
77
69
 
70
+ # @param locale
71
+ # @option opts [Array|Proc] :keys keys to update, if proc call with locale
72
+ # @option opts [String|Proc] value, if proc call with each key
73
+ # @option opts [String|Proc] values, if proc call with all the keys
74
+ def update_locale_data(locale, opts = {})
75
+ locale = locale.to_s
76
+ keys = opts[:keys]
77
+ keys = keys.call(locale) if keys.respond_to?(:call)
78
+ return if keys.empty?
79
+
80
+ values = opts[:values]
81
+ values = values.call(keys, locale) if values.respond_to?(:call)
82
+ values ||= begin
83
+ value = opts[:value] or raise 'pass value or values'
84
+ if value.respond_to?(:call)
85
+ keys.map { |key| value.call(key, locale) }
86
+ else
87
+ [value] * keys.size
88
+ end
89
+ end
90
+ data[locale] = tree(locale).merge!(
91
+ Tree::Siblings.from_flat_pairs keys.map(&:to_s).zip(values)
92
+ ).parent
93
+ end
94
+ end
78
95
  end
@@ -1,3 +1,5 @@
1
+ require 'fileutils'
2
+
1
3
  module I18n
2
4
  module Tasks
3
5
  module Data
@@ -19,6 +21,7 @@ module I18n
19
21
  end
20
22
 
21
23
  def write_tree(path, tree)
24
+ ::FileUtils.mkpath(File.dirname path)
22
25
  ::File.open(path, 'w') { |f|
23
26
  adapter_name, adapter_pattern, adapter = adapter_for(path)
24
27
  adapter_options = (config[adapter_name] || {})[:write]
@@ -1,12 +1,12 @@
1
- require 'i18n/tasks/data/locale_tree'
2
- require 'i18n/tasks/data/router'
1
+ require 'i18n/tasks/data/tree/node'
2
+ require 'i18n/tasks/data/router/pattern_router'
3
+ require 'i18n/tasks/data/router/conservative_router'
3
4
  require 'i18n/tasks/data/file_formats'
4
5
  require 'i18n/tasks/key_pattern_matching'
5
6
 
6
7
  module I18n::Tasks
7
8
  module Data
8
9
  class FileSystemBase
9
- include ::I18n::Tasks::Data::Router
10
10
  include ::I18n::Tasks::Data::FileFormats
11
11
 
12
12
  attr_reader :config
@@ -20,48 +20,32 @@ module I18n::Tasks
20
20
  self.config = config
21
21
  end
22
22
 
23
- def t(key, locale)
24
- get(locale).t(key)
25
- end
26
-
27
- def config=(config)
28
- @config = DEFAULTS.deep_merge((config || {}).with_indifferent_access)
29
- @config[:write] = compile_routes @config[:write]
30
- reload
31
- end
32
-
33
23
  # get locale tree
34
24
  def get(locale)
35
- locale = locale.to_s
36
- @locale_data[locale] ||= begin
37
- hash = config[:read].map do |path|
38
- Dir.glob path % {locale: locale}
39
- end.reduce(:+).map do |locale_file|
40
- load_file locale_file
41
- end.inject({}) do |hash, locale_data|
42
- hash.deep_merge! locale_data || {}
43
- hash
44
- end[locale.to_s] || {}
45
- LocaleTree.new locale, hash.to_hash
46
- end
25
+ locale = locale.to_s
26
+ @trees ||= {}
27
+ @trees[locale] ||= Tree::Siblings[locale => {}].merge!(
28
+ read_locale locale
29
+ )
47
30
  end
48
31
 
49
32
  alias [] get
50
33
 
51
34
  # set locale tree
52
- def set(locale, values)
35
+ def set(locale, tree)
53
36
  locale = locale.to_s
54
- route_values config[:write], values, locale do |path, tree|
55
- write_tree path, tree
37
+ router.route locale, tree do |path, tree_slice|
38
+ write_tree path, tree_slice
56
39
  end
57
- @locale_data[locale] = nil
40
+ @trees.delete(locale) if @trees
41
+ @available_locales = nil
58
42
  end
59
43
 
60
44
  alias []= set
61
45
 
62
46
  # @return self
63
47
  def reload
64
- @locale_data = {}
48
+ @trees = nil
65
49
  @available_locales = nil
66
50
  self
67
51
  end
@@ -85,6 +69,45 @@ module I18n::Tasks
85
69
  locales
86
70
  end
87
71
  end
72
+
73
+ def t(key, locale)
74
+ tree = self[locale.to_s]
75
+ return unless tree
76
+ tree[locale][key].try(:hash_or_value)
77
+ end
78
+
79
+ def config=(config)
80
+ @config = DEFAULTS.deep_merge((config || {}).with_indifferent_access)
81
+ reload
82
+ end
83
+
84
+ protected
85
+
86
+ def read_locale(locale)
87
+ Array(config[:read]).map do |path|
88
+ Dir.glob path % {locale: locale}
89
+ end.reduce(:+).map do |path|
90
+ [path.freeze, load_file(path) || {}]
91
+ end.map do |path, data|
92
+ Data::Tree::Siblings.from_nested_hash(data).tap do |s|
93
+ s.leaves { |x| x.data[:path] = path }
94
+ end
95
+ end.reduce(:merge!) || Tree::Siblings.null
96
+ end
97
+
98
+ def base_locale
99
+ config[:base_locale]
100
+ end
101
+
102
+ def router
103
+ @router ||= begin
104
+ name = @config[:router].presence || 'pattern_router'
105
+ if name[0] != name[0].upcase
106
+ name = "I18n::Tasks::Data::Router::#{name.classify}"
107
+ end
108
+ name.constantize.new(self, @config)
109
+ end
110
+ end
88
111
  end
89
112
  end
90
113
  end