t_t 1.1.0 → 1.2.0
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/.travis.yml +2 -2
- data/cheatsheet.md +11 -9
- data/docs/action_factory.md +4 -6
- data/docs/synchronisation.md +39 -0
- data/lib/t_t.rb +24 -209
- data/lib/t_t/action_factory.rb +7 -20
- data/lib/t_t/base.rb +185 -0
- data/lib/t_t/i18n_sync.rb +138 -0
- data/lib/t_t/rails.rb +63 -0
- data/lib/t_t/tasks.rb +23 -0
- data/readme.md +10 -2
- data/t_t.gemspec +2 -2
- data/tests/lib/action_pack_test.rb +1 -1
- data/tests/lib/i18n_sync_test.rb +97 -0
- data/tests/lib/model_test.rb +2 -2
- data/tests/test_helper.rb +3 -6
- metadata +9 -4
- data/gemfiles/Gemfile.actionpack-3.1.x +0 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 5e2d2727092a795c91c2ed55be7121285f17fe67
|
4
|
+
data.tar.gz: c60c9c93024393bc55c3ab325d8bc5c5137dab9a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ecb7f85dc79c4ad72f4351f6956b37cb5731af57191c11b6fe7a93fc41053466e9006f8adf183c0dfb025bedb8ec53f06eb83cd6b49ffb7437daef752a72d02c
|
7
|
+
data.tar.gz: c50045f1db29052474c838ebdcf7ffa0fdb551d4b3922e198f28361d569dfc98eec32863003985b6251c394addba1a07b9cfff492c5591c52c6d59f529e8b713
|
data/.travis.yml
CHANGED
@@ -1,13 +1,13 @@
|
|
1
|
+
language: ruby
|
2
|
+
cache: bundler
|
1
3
|
rvm:
|
2
4
|
- 1.9.3
|
3
|
-
- rbx-2
|
4
5
|
- jruby
|
5
6
|
- 2.0.0
|
6
7
|
- 2.1
|
7
8
|
- 2.2
|
8
9
|
- ruby-head
|
9
10
|
gemfile:
|
10
|
-
- gemfiles/Gemfile.actionpack-3.1.x
|
11
11
|
- gemfiles/Gemfile.actionpack-3.2.x
|
12
12
|
- gemfiles/Gemfile.actionpack-4.0.x
|
13
13
|
- gemfiles/Gemfile.actionpack-4.1.x
|
data/cheatsheet.md
CHANGED
@@ -124,8 +124,8 @@ For other `active_model` based orms please specify configuration in an initializ
|
|
124
124
|
# attributes:
|
125
125
|
# user: "Email"
|
126
126
|
|
127
|
-
# app/config/
|
128
|
-
TT.config(prefix: :mongoid)
|
127
|
+
# app/config/t_t.rb
|
128
|
+
TT::Rails.config(prefix: :mongoid)
|
129
129
|
```
|
130
130
|
|
131
131
|
## Resources
|
@@ -302,8 +302,8 @@ For example, words related to a user tips is good to place into `tips`, words re
|
|
302
302
|
the gem provides a configuration block:
|
303
303
|
|
304
304
|
```ruby
|
305
|
-
# app/config/
|
306
|
-
TT.config do
|
305
|
+
# app/config/t_t.rb
|
306
|
+
TT::Rails.config do
|
307
307
|
lookup_key_method :tip, :tips
|
308
308
|
lookup_key_method :f, :forms
|
309
309
|
end
|
@@ -317,13 +317,13 @@ all it's not a problem. Let's look at a possible cases:
|
|
317
317
|
|
318
318
|
### You don't have ActionPack or want to use Dos-T outside the views
|
319
319
|
|
320
|
-
`tt` is an instance of `TT::
|
320
|
+
`tt` is an instance of `TT::Base`. To create a variable you need `namespace` & `section` (optional) keys. In the rails
|
321
321
|
environment it's `controller_path` and `action_name`.
|
322
322
|
|
323
323
|
```ruby
|
324
324
|
class EmailApp < Sinatra::Base
|
325
325
|
before do
|
326
|
-
@tt = TT::
|
326
|
+
@tt = TT::Base.new('emails')
|
327
327
|
end
|
328
328
|
|
329
329
|
post '/' do
|
@@ -336,7 +336,7 @@ class EmailSender
|
|
336
336
|
attr_reader :tt
|
337
337
|
|
338
338
|
def initialize
|
339
|
-
@tt = TT::
|
339
|
+
@tt = TT::Base.new('services/email_sender')
|
340
340
|
end
|
341
341
|
|
342
342
|
def work
|
@@ -352,6 +352,8 @@ end
|
|
352
352
|
|
353
353
|
Just specify an orm i18n scope:
|
354
354
|
```ruby
|
355
|
-
# app/config/
|
356
|
-
|
355
|
+
# app/config/t_t.rb
|
356
|
+
|
357
|
+
# activerecord and mongoid is supported out of the box
|
358
|
+
TT::Rails.config(prefix: :mongoid)
|
357
359
|
```
|
data/docs/action_factory.md
CHANGED
@@ -1,10 +1,9 @@
|
|
1
1
|
# Overview
|
2
2
|
|
3
|
-
Dos-T provides
|
3
|
+
Dos-T provides the factory to generate 'action'-translations in a few lines.
|
4
4
|
|
5
5
|
```ruby
|
6
6
|
# config/locales/actions.rb
|
7
|
-
require 't_t/action_factory'
|
8
7
|
|
9
8
|
TT.define_actions(:en, :de) do |f|
|
10
9
|
f.add :see_all, en: "See all %{rs}", de: "Siehe alle %{RS}"
|
@@ -17,6 +16,7 @@ texts. The most popular case is an English "a/an" rule. For example, an applicat
|
|
17
16
|
|
18
17
|
```ruby
|
19
18
|
# config/locales/actions.en.yml
|
19
|
+
|
20
20
|
en:
|
21
21
|
actions:
|
22
22
|
base:
|
@@ -38,14 +38,13 @@ can teach DSL some grammar and it will generates all translation for you:
|
|
38
38
|
|
39
39
|
```ruby
|
40
40
|
# config/locales/actions.rb
|
41
|
-
require 't_t/action_factory'
|
42
41
|
|
43
42
|
TT.define_actions(:en) do |f|
|
44
43
|
f.for(:en) do |l|
|
45
44
|
# defines `a/an` rule for English where:
|
46
45
|
# base - a base action translation or a result of the previous rule processing
|
47
|
-
# a_meta - a action-related metadata (
|
48
|
-
# r_meta - a resource-related metadata (
|
46
|
+
# a_meta - a action-related metadata (specified on adding an action)
|
47
|
+
# r_meta - a resource-related metadata (specified on marking a resource to use a rule)
|
49
48
|
l.rule(:an) { |base, a_meta, r_meta| a_meta }
|
50
49
|
|
51
50
|
# registers a resources which should use the rule
|
@@ -62,7 +61,6 @@ end
|
|
62
61
|
Here an another example with a more complex grammar rules:
|
63
62
|
|
64
63
|
```ruby
|
65
|
-
require 't_t/action_factory'
|
66
64
|
# de:
|
67
65
|
# models:
|
68
66
|
# article:
|
@@ -0,0 +1,39 @@
|
|
1
|
+
# Synchronisation of a translation files
|
2
|
+
|
3
|
+
When you work on a multi-language application it's easy to add a key for one language and forget to add for another.
|
4
|
+
With Dos-T you have a file watcher which watches on the main locale files (`:en` translations by default). Like the asset
|
5
|
+
pipeline in development environment, you can enable the file synchronisation and on each page reload the gem will check
|
6
|
+
if an English translation was changed and apply changes for other languages. To enable it add the next in
|
7
|
+
`config/environments/development.rb`:
|
8
|
+
|
9
|
+
```ruby
|
10
|
+
# config/environments/development.rb
|
11
|
+
|
12
|
+
Rails.application.configure do
|
13
|
+
# other configuration
|
14
|
+
config.tt.sync = true
|
15
|
+
end
|
16
|
+
```
|
17
|
+
|
18
|
+
If your default translation is not English or the translation files located not in `config/locales` use the next
|
19
|
+
configuration:
|
20
|
+
|
21
|
+
```ruby
|
22
|
+
# config/environments/development.rb
|
23
|
+
|
24
|
+
Rails.application.configure do
|
25
|
+
# a custom default locale
|
26
|
+
config.tt.sync = :de
|
27
|
+
# a custom default glob
|
28
|
+
config.tt.sync = { locale: :fr, glob: 'other/locale/folder/**/*.yml' }
|
29
|
+
end
|
30
|
+
```
|
31
|
+
|
32
|
+
Also you can synchronise files without a page reload. Add the next line at the bottom `%rails_root%/Rakefile`:
|
33
|
+
|
34
|
+
```ruby
|
35
|
+
require 't_t/tasks'
|
36
|
+
```
|
37
|
+
|
38
|
+
You will have two additional rake tasks - `tt:s` (synchronises the translation files) and `tt:m` (prints the missing
|
39
|
+
translations for all languages)
|
data/lib/t_t.rb
CHANGED
@@ -1,226 +1,41 @@
|
|
1
|
-
require
|
2
|
-
require "active_support/lazy_load_hooks"
|
3
|
-
require "active_support/multibyte/chars"
|
4
|
-
require "i18n"
|
1
|
+
require 't_t/base'
|
5
2
|
|
6
3
|
module TT
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
def lookup(prefix, base_suffix)
|
13
|
-
prefix ? prefix_lookup(prefix, base_suffix) : simple_lookup(base_suffix)
|
14
|
-
end
|
15
|
-
|
16
|
-
def to_parts(str)
|
17
|
-
str.to_s.underscore.split(/\.|\//)
|
18
|
-
end
|
19
|
-
|
20
|
-
private
|
21
|
-
|
22
|
-
def simple_lookup(base_suffix)
|
23
|
-
lambda do |ns, type|
|
24
|
-
parts = to_parts(ns)
|
25
|
-
model_path = parts.join('.')
|
26
|
-
|
27
|
-
root = "#{ type }.#{ model_path }"
|
28
|
-
|
29
|
-
defaults = []
|
30
|
-
defaults << :"#{ type }.#{ parts.last }" if parts.length > 1
|
31
|
-
if base_suffix
|
32
|
-
defaults << :"#{ type }.#{ base_suffix }"
|
33
|
-
else
|
34
|
-
defaults << type
|
35
|
-
end
|
36
|
-
|
37
|
-
[root, defaults]
|
38
|
-
end
|
39
|
-
end
|
40
|
-
|
41
|
-
def prefix_lookup(prefix, base_suffix)
|
42
|
-
lambda do |ns, type|
|
43
|
-
parts = to_parts(ns)
|
44
|
-
model_path = parts.join('.')
|
45
|
-
|
46
|
-
root = "#{ prefix }.#{ type }.#{ model_path }"
|
47
|
-
defaults = [:"#{ type }.#{ model_path }"]
|
48
|
-
|
49
|
-
if parts.length > 1
|
50
|
-
pure_model = parts.last
|
51
|
-
defaults << :"#{ prefix }.#{ type }.#{ pure_model }"
|
52
|
-
defaults << :"#{ type }.#{ pure_model }"
|
53
|
-
end
|
54
|
-
|
55
|
-
if base_suffix
|
56
|
-
defaults << :"#{ prefix }.#{ type }.#{ base_suffix }"
|
57
|
-
defaults << :"#{ type }.#{ base_suffix }"
|
58
|
-
else
|
59
|
-
defaults << :"#{ prefix }.#{ type }"
|
60
|
-
defaults << type
|
61
|
-
end
|
62
|
-
|
63
|
-
[root, defaults]
|
64
|
-
end
|
65
|
-
end
|
4
|
+
def self.fork(*args, &block)
|
5
|
+
klass = Class.new(Base)
|
6
|
+
klass.config(*args, &block)
|
7
|
+
klass
|
66
8
|
end
|
67
9
|
|
68
|
-
|
69
|
-
|
70
|
-
def lookup_key_method(meth_name, path)
|
71
|
-
class_eval <<-RUBY
|
72
|
-
def #{ meth_name }(key, options = {})
|
73
|
-
I18n.t "\#{ _config.fetch(:ns) }.#{ path }.\#{ key }",
|
74
|
-
{ default: [:"#{ path }.\#{ key }"] }.merge(options)
|
75
|
-
end
|
76
|
-
RUBY
|
77
|
-
end
|
78
|
-
|
79
|
-
def settings(custom = nil)
|
80
|
-
@settings ||= {}
|
81
|
-
|
82
|
-
if custom
|
83
|
-
unknown = custom.keys.detect { |key| ![:downcase, :prefix].include?(key) }
|
84
|
-
if unknown
|
85
|
-
raise ArgumentError, "TT doesn't know `#{ unknown }` option in the configuration"
|
86
|
-
else
|
87
|
-
@settings.merge!(custom)
|
88
|
-
end
|
89
|
-
end
|
90
|
-
|
91
|
-
@settings
|
92
|
-
end
|
93
|
-
end
|
94
|
-
|
95
|
-
lookup_key_method :c, :common
|
96
|
-
|
97
|
-
def initialize(ns, section = nil)
|
98
|
-
@lookup = Utils.lookup(self.class.settings[:prefix], nil)
|
99
|
-
@b_lookup = Utils.lookup(self.class.settings[:prefix], :base)
|
100
|
-
@e_lookup = Utils.lookup(self.class.settings[:prefix], :messages)
|
101
|
-
|
102
|
-
ns = Utils.to_parts(ns).join('.')
|
103
|
-
@config = { ns: ns, root: (section ? "#{ ns }.#{ section }" : ns) }
|
104
|
-
default_model = ns.to_s.singularize
|
105
|
-
|
106
|
-
@config[:attributes] = @lookup.call(default_model, :attributes)
|
107
|
-
@config[:models] = @lookup.call(default_model, :models)
|
108
|
-
@config[:actions] = @b_lookup.call(default_model, :actions)
|
109
|
-
@config[:enums] = @b_lookup.call(default_model, :enums)
|
110
|
-
@config[:errors] = @e_lookup.call(default_model, :errors)
|
111
|
-
|
112
|
-
@downcase = self.class.settings.fetch(:downcase, Utils::DOWNCASE)
|
113
|
-
end
|
114
|
-
|
115
|
-
def a(name, model_name = nil, custom = {})
|
116
|
-
path, defaults = _resolve(@b_lookup, model_name, :actions, name)
|
117
|
-
|
118
|
-
resource = r(model_name)
|
119
|
-
resources = rs(model_name)
|
120
|
-
I18n.t path, {
|
121
|
-
default: defaults, r: @downcase.call(resource, I18n.locale), R: resource,
|
122
|
-
rs: @downcase.call(resources, I18n.locale), RS: resources
|
123
|
-
}.merge!(custom)
|
124
|
-
end
|
125
|
-
|
126
|
-
def attr(name, model_name = nil)
|
127
|
-
path, defaults = _resolve(@lookup, model_name, :attributes, name)
|
128
|
-
I18n.t path, default: defaults
|
129
|
-
end
|
130
|
-
|
131
|
-
def enum(name, kind, model_name = nil)
|
132
|
-
path, defaults = _resolve(@b_lookup, model_name, :enums, "#{ name }.#{ kind }")
|
133
|
-
I18n.t path, default: defaults
|
134
|
-
end
|
135
|
-
|
136
|
-
def e(attr_name, error_name, *args)
|
137
|
-
custom = args.last.is_a?(Hash) ? args.pop : {}
|
138
|
-
model_name = args.first
|
139
|
-
path, defaults = _resolve_errors(model_name, attr_name, error_name)
|
140
|
-
I18n.t path, { default: defaults }.merge!(custom)
|
141
|
-
end
|
142
|
-
|
143
|
-
def r(model_name = nil)
|
144
|
-
rs(model_name, 1)
|
145
|
-
end
|
146
|
-
|
147
|
-
def rs(model_name = nil, count = 10)
|
148
|
-
path, defaults = _resolve(@lookup, model_name, :models, nil)
|
149
|
-
# cut from defaults :"#{ orm }.models", :models
|
150
|
-
I18n.t path, default: defaults[0...-2], count: count
|
151
|
-
end
|
152
|
-
|
153
|
-
def t(key, custom = {})
|
154
|
-
defaults = [:"#{ _config.fetch(:ns) }.common.#{ key }"].concat(Array(custom[:default]))
|
155
|
-
I18n.t "#{ _config.fetch(:root) }.#{ key }", custom.merge(default: defaults)
|
156
|
-
end
|
157
|
-
|
158
|
-
private
|
159
|
-
|
160
|
-
def _config
|
161
|
-
@config
|
162
|
-
end
|
163
|
-
|
164
|
-
def _resolve_errors(model_name, attr_name, error_name)
|
165
|
-
if attr_name == :base
|
166
|
-
_resolve(@e_lookup, model_name, :errors, error_name)
|
167
|
-
else
|
168
|
-
path, _defaults = _resolve(@lookup, model_name, :errors, "#{ attr_name }.#{ error_name }")
|
169
|
-
defaults = _defaults + ["errors.messages.#{ error_name }".to_sym]
|
170
|
-
return path, defaults
|
171
|
-
end
|
172
|
-
end
|
173
|
-
|
174
|
-
def _resolve(lookup, model_name, type, key)
|
175
|
-
paths = model_name ? lookup.call(model_name, type) : _config.fetch(type)
|
176
|
-
if key
|
177
|
-
return "#{ paths.first }.#{ key }", paths.last.map { |i| :"#{ i }.#{ key }" }
|
178
|
-
else
|
179
|
-
return *paths
|
180
|
-
end
|
181
|
-
end
|
182
|
-
|
183
|
-
ActiveSupport.run_load_hooks(:tt, self)
|
10
|
+
def self.config(*args, &block)
|
11
|
+
base.config(*args, &block)
|
184
12
|
end
|
185
13
|
|
186
|
-
def self.
|
187
|
-
|
14
|
+
def self.base(value = nil)
|
15
|
+
@base || Base
|
188
16
|
end
|
189
17
|
|
190
|
-
def self.
|
191
|
-
|
192
|
-
Translator.instance_exec(&block) if block_given?
|
18
|
+
def self.base=(value)
|
19
|
+
@base = value
|
193
20
|
end
|
194
|
-
end
|
195
|
-
|
196
|
-
if defined?(ActionPack) || defined?(ActionMailer)
|
197
|
-
module TT::Helper
|
198
|
-
extend ::ActiveSupport::Concern
|
199
21
|
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
prepend_before_filter { instance_variable_set(:@tt, ::TT::Translator.new(controller_path, action_name)) }
|
204
|
-
end
|
205
|
-
|
206
|
-
private
|
207
|
-
|
208
|
-
def tt(*args)
|
209
|
-
args.empty? ? @tt : @tt.t(*args)
|
210
|
-
end
|
22
|
+
def self.raise_error(base)
|
23
|
+
raise ArgumentError, "t_t: #{ base }"
|
211
24
|
end
|
212
25
|
|
213
|
-
|
214
|
-
|
26
|
+
def self.define_actions(*args)
|
27
|
+
require "t_t/action_factory"
|
28
|
+
f = ActionFactory.new(*args)
|
29
|
+
yield f
|
30
|
+
f.as_hash
|
215
31
|
end
|
216
32
|
|
217
|
-
|
218
|
-
|
33
|
+
def self.const_missing(name)
|
34
|
+
super unless name.to_s == 'Translator'
|
35
|
+
puts ""
|
36
|
+
puts "t_t: TT::Translator is deprecated. Please, use #{ base } instead"
|
37
|
+
base
|
219
38
|
end
|
220
39
|
end
|
221
40
|
|
222
|
-
if defined?(
|
223
|
-
ActiveSupport.on_load(:active_record) do
|
224
|
-
TT.config(prefix: :activerecord)
|
225
|
-
end
|
226
|
-
end
|
41
|
+
require 't_t/rails' if defined?(Rails)
|
data/lib/t_t/action_factory.rb
CHANGED
@@ -1,5 +1,3 @@
|
|
1
|
-
require 't_t/builtin_rules'
|
2
|
-
|
3
1
|
module TT
|
4
2
|
class ActionFactory
|
5
3
|
Action = Struct.new(:base, :rules)
|
@@ -57,17 +55,18 @@ module TT
|
|
57
55
|
end
|
58
56
|
|
59
57
|
def for(key, &block)
|
60
|
-
yield @locales.fetch(key) { raise_error "`#{ key }` is unknown" }
|
58
|
+
yield @locales.fetch(key) { TT.raise_error "`#{ key }` is unknown" }
|
61
59
|
end
|
62
60
|
|
63
61
|
def activate_rules(*list)
|
62
|
+
require 't_t/builtin_rules'
|
64
63
|
list.each { |rkey| BuiltinRules.send(rkey, self) }
|
65
64
|
end
|
66
65
|
|
67
66
|
def add(akey, list)
|
68
67
|
@locales.each do |lkey, locale|
|
69
68
|
unless action = list[lkey]
|
70
|
-
raise_error "action `#{ akey }` is missing for `#{ lkey }` locale"
|
69
|
+
TT.raise_error "action `#{ akey }` is missing for `#{ lkey }` locale"
|
71
70
|
end
|
72
71
|
|
73
72
|
action = Action.new(action, []) if action.is_a?(String)
|
@@ -75,10 +74,10 @@ module TT
|
|
75
74
|
if action.is_a?(Action)
|
76
75
|
action.rules.each do |rule|
|
77
76
|
next if locale.knows_rule?(rule.key)
|
78
|
-
raise_error "`#{ rule.key }` is an unknown rule for `#{ lkey }` locale"
|
77
|
+
TT.raise_error "`#{ rule.key }` is an unknown rule for `#{ lkey }` locale"
|
79
78
|
end
|
80
79
|
else
|
81
|
-
raise_error "the value of `#{ akey }` action for `#{ lkey }` locale has a wrong type"
|
80
|
+
TT.raise_error "the value of `#{ akey }` action for `#{ lkey }` locale has a wrong type"
|
82
81
|
end
|
83
82
|
|
84
83
|
@actions[lkey][akey] = action
|
@@ -91,11 +90,11 @@ module TT
|
|
91
90
|
|
92
91
|
def add_exception(mkey, schema)
|
93
92
|
schema.each do |lkey, list|
|
94
|
-
raise_error("`#{ lkey }` is an unknown locale") unless @locales.has_key?(lkey)
|
93
|
+
TT.raise_error("`#{ lkey }` is an unknown locale") unless @locales.has_key?(lkey)
|
95
94
|
|
96
95
|
list.each do |akey, str|
|
97
96
|
unless @actions[lkey].has_key?(akey)
|
98
|
-
raise_error "`#{ akey }` action is not specified. Do it before add an exception"
|
97
|
+
TT.raise_error "`#{ akey }` action is not specified. Do it before add an exception"
|
99
98
|
end
|
100
99
|
|
101
100
|
@exceptions[lkey][akey] ||= {}
|
@@ -121,17 +120,5 @@ module TT
|
|
121
120
|
hash.merge!(lkey => { actions: actions })
|
122
121
|
end
|
123
122
|
end
|
124
|
-
|
125
|
-
private
|
126
|
-
|
127
|
-
def raise_error(base)
|
128
|
-
raise ArgumentError, "t_t: #{ base }"
|
129
|
-
end
|
130
|
-
end
|
131
|
-
|
132
|
-
def self.define_actions(*args)
|
133
|
-
f = ActionFactory.new(*args)
|
134
|
-
yield f
|
135
|
-
f.as_hash
|
136
123
|
end
|
137
124
|
end
|
data/lib/t_t/base.rb
ADDED
@@ -0,0 +1,185 @@
|
|
1
|
+
require "active_support/inflector"
|
2
|
+
require "active_support/lazy_load_hooks"
|
3
|
+
require "active_support/multibyte/chars"
|
4
|
+
require "i18n"
|
5
|
+
|
6
|
+
module TT
|
7
|
+
module Utils
|
8
|
+
extend self
|
9
|
+
|
10
|
+
DOWNCASE = lambda { |str, locale| (locale == :en) ? str.downcase : str.mb_chars.downcase.to_s }
|
11
|
+
|
12
|
+
def lookup(prefix, base_suffix)
|
13
|
+
prefix ? prefix_lookup(prefix, base_suffix) : simple_lookup(base_suffix)
|
14
|
+
end
|
15
|
+
|
16
|
+
def to_parts(str)
|
17
|
+
str.to_s.underscore.split(/\.|\//)
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def simple_lookup(base_suffix)
|
23
|
+
lambda do |ns, type|
|
24
|
+
parts = to_parts(ns)
|
25
|
+
model_path = parts.join('.')
|
26
|
+
|
27
|
+
root = "#{ type }.#{ model_path }"
|
28
|
+
|
29
|
+
defaults = []
|
30
|
+
defaults << :"#{ type }.#{ parts.last }" if parts.length > 1
|
31
|
+
if base_suffix
|
32
|
+
defaults << :"#{ type }.#{ base_suffix }"
|
33
|
+
else
|
34
|
+
defaults << type
|
35
|
+
end
|
36
|
+
|
37
|
+
[root, defaults]
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def prefix_lookup(prefix, base_suffix)
|
42
|
+
lambda do |ns, type|
|
43
|
+
parts = to_parts(ns)
|
44
|
+
model_path = parts.join('.')
|
45
|
+
|
46
|
+
root = "#{ prefix }.#{ type }.#{ model_path }"
|
47
|
+
defaults = [:"#{ type }.#{ model_path }"]
|
48
|
+
|
49
|
+
if parts.length > 1
|
50
|
+
pure_model = parts.last
|
51
|
+
defaults << :"#{ prefix }.#{ type }.#{ pure_model }"
|
52
|
+
defaults << :"#{ type }.#{ pure_model }"
|
53
|
+
end
|
54
|
+
|
55
|
+
if base_suffix
|
56
|
+
defaults << :"#{ prefix }.#{ type }.#{ base_suffix }"
|
57
|
+
defaults << :"#{ type }.#{ base_suffix }"
|
58
|
+
else
|
59
|
+
defaults << :"#{ prefix }.#{ type }"
|
60
|
+
defaults << type
|
61
|
+
end
|
62
|
+
|
63
|
+
[root, defaults]
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
class Base
|
69
|
+
def self.lookup_key_method(meth_name, path)
|
70
|
+
class_eval <<-RUBY
|
71
|
+
def #{ meth_name }(key, options = {})
|
72
|
+
I18n.t "\#{ _config.fetch(:ns) }.#{ path }.\#{ key }",
|
73
|
+
{ default: [:"#{ path }.\#{ key }"] }.merge(options)
|
74
|
+
end
|
75
|
+
RUBY
|
76
|
+
end
|
77
|
+
|
78
|
+
def self.config(custom = nil, &block)
|
79
|
+
@settings ||= {}
|
80
|
+
|
81
|
+
if custom
|
82
|
+
unknown = custom.keys.detect { |key| ![:downcase, :prefix].include?(key) }
|
83
|
+
if unknown
|
84
|
+
TT.raise_error "`#{ unknown }` is a wrong key in the configuration"
|
85
|
+
else
|
86
|
+
@settings.merge!(custom)
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
instance_exec(&block) if block_given?
|
91
|
+
|
92
|
+
@settings
|
93
|
+
end
|
94
|
+
|
95
|
+
lookup_key_method :c, :common
|
96
|
+
|
97
|
+
def initialize(ns, section = nil)
|
98
|
+
@lookup = Utils.lookup(self.class.config[:prefix], nil)
|
99
|
+
@b_lookup = Utils.lookup(self.class.config[:prefix], :base)
|
100
|
+
@e_lookup = Utils.lookup(self.class.config[:prefix], :messages)
|
101
|
+
|
102
|
+
ns = Utils.to_parts(ns).join('.')
|
103
|
+
@config = { ns: ns, root: (section ? "#{ ns }.#{ section }" : ns) }
|
104
|
+
default_model = ns.to_s.singularize
|
105
|
+
|
106
|
+
@config[:attributes] = @lookup.call(default_model, :attributes)
|
107
|
+
@config[:models] = @lookup.call(default_model, :models)
|
108
|
+
@config[:actions] = @b_lookup.call(default_model, :actions)
|
109
|
+
@config[:enums] = @b_lookup.call(default_model, :enums)
|
110
|
+
@config[:errors] = @e_lookup.call(default_model, :errors)
|
111
|
+
|
112
|
+
@downcase = self.class.config.fetch(:downcase, Utils::DOWNCASE)
|
113
|
+
end
|
114
|
+
|
115
|
+
def a(name, model_name = nil, custom = {})
|
116
|
+
path, defaults = _resolve(@b_lookup, model_name, :actions, name)
|
117
|
+
|
118
|
+
resource = r(model_name)
|
119
|
+
resources = rs(model_name)
|
120
|
+
I18n.t path, {
|
121
|
+
default: defaults, r: @downcase.call(resource, I18n.locale), R: resource,
|
122
|
+
rs: @downcase.call(resources, I18n.locale), RS: resources
|
123
|
+
}.merge!(custom)
|
124
|
+
end
|
125
|
+
|
126
|
+
def attr(name, model_name = nil)
|
127
|
+
path, defaults = _resolve(@lookup, model_name, :attributes, name)
|
128
|
+
I18n.t path, default: defaults
|
129
|
+
end
|
130
|
+
|
131
|
+
def enum(name, kind, model_name = nil)
|
132
|
+
path, defaults = _resolve(@b_lookup, model_name, :enums, "#{ name }.#{ kind }")
|
133
|
+
I18n.t path, default: defaults
|
134
|
+
end
|
135
|
+
|
136
|
+
def e(attr_name, error_name, *args)
|
137
|
+
custom = args.last.is_a?(Hash) ? args.pop : {}
|
138
|
+
model_name = args.first
|
139
|
+
path, defaults = _resolve_errors(model_name, attr_name, error_name)
|
140
|
+
I18n.t path, { default: defaults }.merge!(custom)
|
141
|
+
end
|
142
|
+
|
143
|
+
def r(model_name = nil)
|
144
|
+
rs(model_name, 1)
|
145
|
+
end
|
146
|
+
|
147
|
+
def rs(model_name = nil, count = 10)
|
148
|
+
path, defaults = _resolve(@lookup, model_name, :models, nil)
|
149
|
+
# cut from defaults :"#{ orm }.models", :models
|
150
|
+
I18n.t path, default: defaults[0...-2], count: count
|
151
|
+
end
|
152
|
+
|
153
|
+
def t(key, custom = {})
|
154
|
+
defaults = [:"#{ _config.fetch(:ns) }.common.#{ key }"].concat(Array(custom[:default]))
|
155
|
+
I18n.t "#{ _config.fetch(:root) }.#{ key }", custom.merge(default: defaults)
|
156
|
+
end
|
157
|
+
|
158
|
+
private
|
159
|
+
|
160
|
+
def _config
|
161
|
+
@config
|
162
|
+
end
|
163
|
+
|
164
|
+
def _resolve_errors(model_name, attr_name, error_name)
|
165
|
+
if attr_name == :base
|
166
|
+
_resolve(@e_lookup, model_name, :errors, error_name)
|
167
|
+
else
|
168
|
+
path, _defaults = _resolve(@lookup, model_name, :errors, "#{ attr_name }.#{ error_name }")
|
169
|
+
defaults = _defaults + ["errors.messages.#{ error_name }".to_sym]
|
170
|
+
return path, defaults
|
171
|
+
end
|
172
|
+
end
|
173
|
+
|
174
|
+
def _resolve(lookup, model_name, type, key)
|
175
|
+
paths = model_name ? lookup.call(model_name, type) : _config.fetch(type)
|
176
|
+
if key
|
177
|
+
return "#{ paths.first }.#{ key }", paths.last.map { |i| :"#{ i }.#{ key }" }
|
178
|
+
else
|
179
|
+
return *paths
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|
183
|
+
ActiveSupport.run_load_hooks(:tt, self)
|
184
|
+
end
|
185
|
+
end
|
@@ -0,0 +1,138 @@
|
|
1
|
+
require 'active_support/file_update_checker'
|
2
|
+
require 'yaml'
|
3
|
+
|
4
|
+
module TT
|
5
|
+
class I18nSync
|
6
|
+
MARK = ":t_t: "
|
7
|
+
|
8
|
+
module Utils
|
9
|
+
extend self
|
10
|
+
|
11
|
+
def flat_hash(value, key = nil)
|
12
|
+
case value
|
13
|
+
when Hash
|
14
|
+
value.inject({}) { |r, (k, v)| r.merge! flat_hash(v, [key, k].compact.join('/')) }
|
15
|
+
else
|
16
|
+
{ key => value }
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def flat_file(*args)
|
21
|
+
flat_hash(load_file(*args))
|
22
|
+
end
|
23
|
+
|
24
|
+
def load_file(path, locale, &block)
|
25
|
+
content = YAML.load_file(path)
|
26
|
+
yield content if block_given?
|
27
|
+
content.fetch(locale.to_s) do
|
28
|
+
TT.raise_error "expected #{ path } should contain `#{ locale }` translations"
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def sync_file(path, locale, standard)
|
33
|
+
old_review = {}
|
34
|
+
source = load_file(path, locale) { |content| old_review.merge!(content.fetch('review', {})) }
|
35
|
+
new_review = {}
|
36
|
+
content = { locale => sync_level(standard, source, new_review) }
|
37
|
+
review = old_review.merge(flat_hash(new_review))
|
38
|
+
content['review'] = review unless review.empty?
|
39
|
+
write_file(path, content)
|
40
|
+
end
|
41
|
+
|
42
|
+
def write_file(path, content)
|
43
|
+
File.open("#{ path }", "wb") { |stream| YAML.dump(content, stream, line_width: 1000) }
|
44
|
+
end
|
45
|
+
|
46
|
+
private
|
47
|
+
|
48
|
+
def sync_level(st_level, source, review)
|
49
|
+
level = st_level.inject({}) do |r, (key, st_node)|
|
50
|
+
node = source[key]
|
51
|
+
|
52
|
+
r[key] = case st_node
|
53
|
+
when Hash
|
54
|
+
sub_review = {}
|
55
|
+
sub_level = sync_level(st_node, (node.is_a?(Hash) ? node : {}), sub_review)
|
56
|
+
review[key] = sub_review unless sub_review.empty?
|
57
|
+
sub_level
|
58
|
+
when Array then node.is_a?(Array) ? node : st_node.map { |v| "#{ MARK }#{ v }" }
|
59
|
+
else
|
60
|
+
node.nil? ? "#{ MARK }#{ st_node }" : node
|
61
|
+
end
|
62
|
+
r
|
63
|
+
end
|
64
|
+
|
65
|
+
(source.keys - st_level.keys).each { |key| review[key] = source[key] }
|
66
|
+
|
67
|
+
level
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
class FileGroup
|
72
|
+
attr_reader :st_locale, :standard, :list
|
73
|
+
|
74
|
+
def initialize(st_locale, standard, list)
|
75
|
+
@st_locale = st_locale
|
76
|
+
@standard = standard
|
77
|
+
@list = list
|
78
|
+
end
|
79
|
+
|
80
|
+
def execute
|
81
|
+
file_updated_at = File.mtime(standard)
|
82
|
+
return if file_updated_at == @prev_updated_at
|
83
|
+
|
84
|
+
st_source = Utils.load_file(standard, st_locale)
|
85
|
+
list.each { |l, path| Utils.sync_file(path, l, st_source) }
|
86
|
+
|
87
|
+
@prev_updated_at = file_updated_at
|
88
|
+
end
|
89
|
+
|
90
|
+
def missed
|
91
|
+
flat_list = list.inject({}) { |r, (l, path)| r.merge!(l => Utils.flat_file(path, l)) }
|
92
|
+
|
93
|
+
Utils.flat_file(standard, st_locale).inject([]) do |list, (k, st_v)|
|
94
|
+
item = flat_list.inject({ st_locale => st_v }) { |r, (l, h)| r.merge!(l => h[k]) }
|
95
|
+
list << item if item.any? { |l, v| v.nil? }
|
96
|
+
list
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
attr_reader :checker, :groups
|
102
|
+
|
103
|
+
def initialize(st_locale, files)
|
104
|
+
@groups = []
|
105
|
+
|
106
|
+
files.inject({}) do |r, file|
|
107
|
+
parts = file.split('.')
|
108
|
+
k = parts[0...-2].join('.')
|
109
|
+
l = File.basename(parts[-2])
|
110
|
+
r[k] ||= {}
|
111
|
+
r[k][l] = file
|
112
|
+
r
|
113
|
+
end.each_value do |group|
|
114
|
+
locales = group.keys
|
115
|
+
next unless locales.include?(st_locale) && locales.size > 1
|
116
|
+
list = group.reject { |l, v| l == st_locale }
|
117
|
+
groups << FileGroup.new(st_locale, group[st_locale], list)
|
118
|
+
end
|
119
|
+
|
120
|
+
@checker = ActiveSupport::FileUpdateChecker.new(groups.map(&:standard)) { execute }
|
121
|
+
end
|
122
|
+
|
123
|
+
def execute
|
124
|
+
groups.each(&:execute)
|
125
|
+
end
|
126
|
+
|
127
|
+
def missed
|
128
|
+
groups.inject({}) do |r, group|
|
129
|
+
unless (list = group.missed).empty?
|
130
|
+
base_path = group.standard.split(group.st_locale)[0]
|
131
|
+
key = "#{ base_path }(*).yml"
|
132
|
+
r[key] = list
|
133
|
+
end
|
134
|
+
r
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
data/lib/t_t/rails.rb
ADDED
@@ -0,0 +1,63 @@
|
|
1
|
+
options = {}
|
2
|
+
if defined?(Mongoid)
|
3
|
+
options[:prefix] = :mongoid
|
4
|
+
elsif defined?(ActiveRecord)
|
5
|
+
options[:prefix] = :activerecord
|
6
|
+
end
|
7
|
+
|
8
|
+
TT.base = TT::Rails = TT.fork(options) do
|
9
|
+
def self.sync(value = nil)
|
10
|
+
@sync = value unless value.nil?
|
11
|
+
@sync
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
module TT
|
16
|
+
module Helper
|
17
|
+
extend ::ActiveSupport::Concern
|
18
|
+
|
19
|
+
included do
|
20
|
+
helper_method :tt
|
21
|
+
|
22
|
+
prepend_before_filter { instance_variable_set(:@tt, ::TT::Rails.new(controller_path, action_name)) }
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
def tt(*args)
|
28
|
+
args.empty? ? @tt : @tt.t(*args)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
class Railtie < ::Rails::Railtie
|
33
|
+
config.tt = ActiveSupport::OrderedOptions.new
|
34
|
+
|
35
|
+
config.after_initialize do |app|
|
36
|
+
if options = app.config.tt.sync
|
37
|
+
require 't_t/i18n_sync'
|
38
|
+
|
39
|
+
locale = :en
|
40
|
+
glob = 'config/locales/**/*.yml'
|
41
|
+
if options.is_a?(Symbol) || options.is_a?(String)
|
42
|
+
locale = options
|
43
|
+
elsif options.is_a?(Hash)
|
44
|
+
locale = options[:locale] if options.has_key?(:locale)
|
45
|
+
glob = options[:glob] if options.has_key?(:glob)
|
46
|
+
end
|
47
|
+
|
48
|
+
file_sync = ::TT::I18nSync.new(locale.to_s, Dir.glob(glob))
|
49
|
+
TT::Rails.sync(file_sync)
|
50
|
+
::Rails.application.reloaders << file_sync.checker
|
51
|
+
ActionDispatch::Reloader.to_prepare { file_sync.checker.execute_if_updated }
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
ActiveSupport.on_load(:action_controller) do
|
58
|
+
include ::TT::Helper
|
59
|
+
end
|
60
|
+
|
61
|
+
ActiveSupport.on_load(:action_mailer) do
|
62
|
+
include ::TT::Helper
|
63
|
+
end
|
data/lib/t_t/tasks.rb
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
namespace :tt do
|
2
|
+
desc "Synchronises the translation file groups"
|
3
|
+
task :s => :environment do
|
4
|
+
if sync = TT::Rails.sync
|
5
|
+
sync.execute
|
6
|
+
else
|
7
|
+
puts "t_t: Please, setup the synchronisation first"
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
desc "Shows a missed keys in the translation file groups"
|
12
|
+
task :m => [:s] do
|
13
|
+
if sync = TT::Rails.sync
|
14
|
+
sync.missed.each do |group, list|
|
15
|
+
puts "# #{ group }"
|
16
|
+
puts line.inject("") { |r, (k, v)| r + "#{ k.upcase }: #{ v.to_s.encode('utf-8') }\n" }
|
17
|
+
puts '---'
|
18
|
+
end
|
19
|
+
else
|
20
|
+
puts "t_t: Please, setup the synchronisation first"
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
data/readme.md
CHANGED
@@ -87,16 +87,24 @@ The result will be the next:
|
|
87
87
|
= link_to 'Add a new user', new_user_path
|
88
88
|
```
|
89
89
|
|
90
|
+
## Additional features
|
91
|
+
|
92
|
+
#### [The action factory](./docs/action_factory.md)
|
93
|
+
|
94
|
+
#### [Synchronisation of a translation files](./docs/synchronisation.md)
|
95
|
+
|
90
96
|
## Setup
|
91
97
|
|
92
98
|
Just add `gem "t_t"` into your Gemfile and run `bundle`.
|
93
99
|
|
94
100
|
## Requirements
|
95
101
|
|
96
|
-
Dos-T is tested against Ruby 1.9.3
|
102
|
+
Dos-T is tested against Ruby 1.9.3+ & JRuby(1.9+ compatible). If your application uses Ruby on Rails the framework version should be 3.2+
|
97
103
|
|
98
104
|
## Changelog
|
99
|
-
|
105
|
+
- 1.2.0
|
106
|
+
- Deprecate TT::Translator in favour of TT::Base & TT::Rails
|
107
|
+
- Introduce a real-time watcher for [translation file synchronisation](./docs/synchronisation.md)
|
100
108
|
- 1.1.0:
|
101
109
|
- Added [the action factory](./docs/action_factory.md)
|
102
110
|
- Improve #attr, #r, #rs methods to make them more compatible with ActiveModel methods
|
data/t_t.gemspec
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
|
3
3
|
Gem::Specification.new do |spec|
|
4
4
|
spec.name = "t_t"
|
5
|
-
spec.version = "1.
|
5
|
+
spec.version = "1.2.0"
|
6
6
|
spec.authors = ["Sergey Pchelintsev"]
|
7
7
|
spec.email = ["mail@sergeyp.me"]
|
8
8
|
spec.summary = %q{An opinioned I18n helper}
|
@@ -18,7 +18,7 @@ Gem::Specification.new do |spec|
|
|
18
18
|
spec.add_dependency "i18n", ">= 0.6.0"
|
19
19
|
spec.add_dependency "activesupport", ">= 3.0.0"
|
20
20
|
|
21
|
-
spec.add_development_dependency "
|
21
|
+
spec.add_development_dependency "rails", ">= 3.0.0"
|
22
22
|
spec.add_development_dependency "bundler", "~> 1.7"
|
23
23
|
spec.add_development_dependency "minitest", ">= 4.7"
|
24
24
|
spec.add_development_dependency "rack-test"
|
@@ -11,7 +11,7 @@ describe "ActionPack integration" do
|
|
11
11
|
end
|
12
12
|
|
13
13
|
it 'returns tt instance if the method was called without args' do
|
14
|
-
assert_equal @controller.tt.class, TT::
|
14
|
+
assert_equal @controller.tt.class, TT::Rails
|
15
15
|
end
|
16
16
|
|
17
17
|
it 'calls #t if args were passed' do
|
@@ -0,0 +1,97 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
describe 'I18n synchronisation' do
|
4
|
+
it 'add a missing keys' do
|
5
|
+
store = {
|
6
|
+
'en' => { 'a' => 'a', 'b' => 'b', 'c' => { 'd' => 'd' }, 'e' => { 'f' => ['k', 'l'] } },
|
7
|
+
'de' => { 'b' => 'de-b' }
|
8
|
+
}
|
9
|
+
|
10
|
+
YAML.stub :load_file, store do
|
11
|
+
group = TT::I18nSync::FileGroup.new('en', __FILE__, { 'de' => 'de.yml' })
|
12
|
+
expectation = lambda do |path, content|
|
13
|
+
assert_equal path, 'de.yml'
|
14
|
+
assert_equal content, {
|
15
|
+
'de' => {
|
16
|
+
'a' => ':t_t: a', 'b' => 'de-b',
|
17
|
+
'c' => { 'd' => ':t_t: d' },
|
18
|
+
'e' => { 'f' => [':t_t: k', ':t_t: l'] }
|
19
|
+
}
|
20
|
+
}
|
21
|
+
end
|
22
|
+
|
23
|
+
utils = TT::I18nSync::Utils
|
24
|
+
utils.stub(:write_file, expectation) { group.execute }
|
25
|
+
|
26
|
+
should_not_call_twice = proc { assert false, "should not call twice" }
|
27
|
+
utils.stub(:write_file, should_not_call_twice) { group.execute }
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
it 'saves the previous review and adds a new' do
|
32
|
+
store = {
|
33
|
+
'review' => { 'c/d' => 'c', 'r' => 'r' },
|
34
|
+
'en' => { 'a' => 'a', 'b' => 'b', 'c' => { 'd' => 'd' } },
|
35
|
+
'de' => { 'b' => 'de-b' }
|
36
|
+
}
|
37
|
+
YAML.stub :load_file, store do
|
38
|
+
group = TT::I18nSync::FileGroup.new('de', __FILE__, { 'en' => 'en.yml' })
|
39
|
+
expectation = lambda do |path, content|
|
40
|
+
assert_equal path, 'en.yml'
|
41
|
+
assert_equal content['en'], { 'b' => 'b' }
|
42
|
+
assert_equal content['review'].sort, [['a', 'a'], ['c/d', 'd'], ['r', 'r']]
|
43
|
+
end
|
44
|
+
|
45
|
+
utils = TT::I18nSync::Utils
|
46
|
+
utils.stub(:write_file, expectation) { group.execute }
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
it 'combines files into a full groups' do
|
51
|
+
groupMock = Struct.new(:locale, :standard, :list) do
|
52
|
+
def execute
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
TT::I18nSync::FileGroup.stub(:new, proc { |*args| groupMock.new(*args) }) do
|
57
|
+
sync = TT::I18nSync.new('de', [
|
58
|
+
'config/locales/fr.yml', 'config/locales/views.bg.yml', 'config/locales/models/orm.nl.yml',
|
59
|
+
'config/locales/de.yml', 'config/locales/views.de.yml', 'config/locales/models/orm.en-US.yml',
|
60
|
+
'config/locales/es.yml', 'config/locales/views.en-US.yml', 'config/locales/models/orm.de.yml',
|
61
|
+
'config/locales/en-US.yml', 'config/locales/views.es.yml', 'config/locales/models/orm.es.yml',
|
62
|
+
'config/locales/fix.en-US.yml', 'config/locales/skip.de.yml'
|
63
|
+
])
|
64
|
+
|
65
|
+
assert_equal 3, sync.groups.length
|
66
|
+
|
67
|
+
sync.groups[0].tap do |group|
|
68
|
+
assert_equal 'config/locales/de.yml', group.standard
|
69
|
+
assert_equal [
|
70
|
+
['en-US', 'config/locales/en-US.yml'],
|
71
|
+
['es', 'config/locales/es.yml'],
|
72
|
+
['fr', 'config/locales/fr.yml']
|
73
|
+
], group.list.sort
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
it 'shows all missed translations' do
|
79
|
+
store = {
|
80
|
+
'en' => { 'a' => 'a', 'b' => 'b', 'c' => { 'd' => 'd' } },
|
81
|
+
'de' => { 'b' => 'de-b' }
|
82
|
+
}
|
83
|
+
YAML.stub :load_file, store do
|
84
|
+
sync = TT::I18nSync.new('en', ['en.yml', 'de.yml'])
|
85
|
+
result = sync.missed
|
86
|
+
|
87
|
+
assert_equal 1, result.size
|
88
|
+
assert_equal '(*).yml', result.keys.first
|
89
|
+
|
90
|
+
result['(*).yml'].tap do |list|
|
91
|
+
assert_equal 2, list.size
|
92
|
+
assert_equal({ 'en' => 'a', 'de' => nil }, list.first)
|
93
|
+
assert_equal({ 'en' => 'd', 'de' => nil }, list.last)
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
data/tests/lib/model_test.rb
CHANGED
@@ -2,7 +2,7 @@ require 'test_helper'
|
|
2
2
|
|
3
3
|
describe 'Methods related to models' do
|
4
4
|
before do
|
5
|
-
@tt = TT::
|
5
|
+
@tt = TT::Rails.new("admin/users", "spec")
|
6
6
|
end
|
7
7
|
|
8
8
|
describe 'actions' do
|
@@ -134,7 +134,7 @@ describe 'Methods related to models' do
|
|
134
134
|
|
135
135
|
describe 'resource names' do
|
136
136
|
before do
|
137
|
-
@tt = TT::
|
137
|
+
@tt = TT::Rails.new('public/people')
|
138
138
|
load_i18n({
|
139
139
|
models: { person: { one: "whatever", other: "whatever" }, user: { one: "User", other: "Users" } },
|
140
140
|
activerecord: { models: {
|
data/tests/test_helper.rb
CHANGED
@@ -1,14 +1,11 @@
|
|
1
|
-
# emulate activerecord presence
|
2
|
-
ActiveRecord = nil
|
3
|
-
|
4
1
|
require "minitest/autorun"
|
5
2
|
require "minitest/mock"
|
6
3
|
require "rack/test"
|
7
|
-
require "action_controller"
|
4
|
+
require "action_controller/railtie"
|
5
|
+
require "active_record/railtie"
|
8
6
|
require "t_t"
|
9
|
-
require "t_t/
|
7
|
+
require "t_t/i18n_sync"
|
10
8
|
|
11
|
-
ActiveSupport.run_load_hooks(:active_record, self)
|
12
9
|
ViewTranslator = TT.fork do
|
13
10
|
lookup_key_method :f, :form
|
14
11
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: t_t
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Sergey Pchelintsev
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2016-
|
11
|
+
date: 2016-03-10 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: i18n
|
@@ -39,7 +39,7 @@ dependencies:
|
|
39
39
|
- !ruby/object:Gem::Version
|
40
40
|
version: 3.0.0
|
41
41
|
- !ruby/object:Gem::Dependency
|
42
|
-
name:
|
42
|
+
name: rails
|
43
43
|
requirement: !ruby/object:Gem::Requirement
|
44
44
|
requirements:
|
45
45
|
- - ">="
|
@@ -121,20 +121,25 @@ files:
|
|
121
121
|
- Rakefile
|
122
122
|
- cheatsheet.md
|
123
123
|
- docs/action_factory.md
|
124
|
+
- docs/synchronisation.md
|
124
125
|
- examples/simple_app.yml
|
125
|
-
- gemfiles/Gemfile.actionpack-3.1.x
|
126
126
|
- gemfiles/Gemfile.actionpack-3.2.x
|
127
127
|
- gemfiles/Gemfile.actionpack-4.0.x
|
128
128
|
- gemfiles/Gemfile.actionpack-4.1.x
|
129
129
|
- gemfiles/Gemfile.actionpack-4.2.x
|
130
130
|
- lib/t_t.rb
|
131
131
|
- lib/t_t/action_factory.rb
|
132
|
+
- lib/t_t/base.rb
|
132
133
|
- lib/t_t/builtin_rules.rb
|
134
|
+
- lib/t_t/i18n_sync.rb
|
135
|
+
- lib/t_t/rails.rb
|
136
|
+
- lib/t_t/tasks.rb
|
133
137
|
- readme.md
|
134
138
|
- t_t.gemspec
|
135
139
|
- tests/lib/action_factory_test.rb
|
136
140
|
- tests/lib/action_pack_test.rb
|
137
141
|
- tests/lib/builtin_rules_test.rb
|
142
|
+
- tests/lib/i18n_sync_test.rb
|
138
143
|
- tests/lib/model_test.rb
|
139
144
|
- tests/lib/view_test.rb
|
140
145
|
- tests/test_helper.rb
|