t_t 1.1.0 → 1.2.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|