t_t 0.0.1 → 1.0.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 +15 -0
- data/cheatsheet.md +307 -0
- data/examples/simple_app.yml +0 -0
- data/gemfiles/Gemfile.actionpack-3.1.x +4 -0
- data/gemfiles/Gemfile.actionpack-3.2.x +4 -0
- data/gemfiles/Gemfile.actionpack-4.0.x +4 -0
- data/gemfiles/Gemfile.actionpack-4.1.x +4 -0
- data/gemfiles/Gemfile.actionpack-4.2.x +4 -0
- data/lib/t_t.rb +159 -32
- data/readme.md +96 -0
- data/t_t.gemspec +1 -1
- data/tests/lib/action_pack_test.rb +2 -0
- data/tests/lib/model_test.rb +162 -0
- data/tests/lib/view_test.rb +70 -0
- data/tests/test_helper.rb +18 -35
- metadata +15 -6
- data/LICENSE.txt +0 -22
- data/README.md +0 -91
- data/tests/lib/t_t_test.rb +0 -146
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d836d08bfcb2cd0c0ddc4cf71c7ff13bc8d95aba
|
4
|
+
data.tar.gz: 46616d020eef370af7a34e6dd672262be6dcf477
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 8b0904544cbcce8598217fae39dacc61a7c2940298f9f096a1e24ff36828b06a756fdceef79f01aeafccbc20580ebd49018ee6f72e7a8d52ee0af9db18b4bb91
|
7
|
+
data.tar.gz: d16e1835b588593536162b9dd9aa5541dcf9ea8bdc672d7de552f172fefdcc8231e354c9435adf85a893f386b3a1f5475bc62e5eeb70a937c4f47db753d958dd
|
data/.travis.yml
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
rvm:
|
2
|
+
- 1.9.3
|
3
|
+
- rbx-2
|
4
|
+
- jruby
|
5
|
+
- 2.0.0
|
6
|
+
- 2.1
|
7
|
+
- 2.2
|
8
|
+
- ruby-head
|
9
|
+
gemfile:
|
10
|
+
- gemfiles/Gemfile.actionpack-3.1.x
|
11
|
+
- gemfiles/Gemfile.actionpack-3.2.x
|
12
|
+
- gemfiles/Gemfile.actionpack-4.0.x
|
13
|
+
- gemfiles/Gemfile.actionpack-4.1.x
|
14
|
+
- gemfiles/Gemfile.actionpack-4.2.x
|
15
|
+
- Gemfile
|
data/cheatsheet.md
ADDED
@@ -0,0 +1,307 @@
|
|
1
|
+
# Dos-T cheatsheet
|
2
|
+
|
3
|
+
## Commons
|
4
|
+
The **widely used words** across the application such as "Back", "Cancel", "Save" are the prime candidates to put in **common**:
|
5
|
+
|
6
|
+
```ruby
|
7
|
+
# en:
|
8
|
+
# common:
|
9
|
+
# back: "Back"
|
10
|
+
|
11
|
+
tt.c :back # => "Back"
|
12
|
+
```
|
13
|
+
|
14
|
+
Time to time there is a need to **override commons** for a section (controller in rails environment). To do so, just **add 'common' sub-key** in the section translations:
|
15
|
+
|
16
|
+
```ruby
|
17
|
+
# en:
|
18
|
+
# common:
|
19
|
+
# copy: "Copy"
|
20
|
+
# users:
|
21
|
+
# common:
|
22
|
+
# copy: "Duplicate"
|
23
|
+
|
24
|
+
# app/views/documents/index.haml
|
25
|
+
= tt.c :copy # => "Copy"
|
26
|
+
|
27
|
+
# app/views/users/index.haml
|
28
|
+
= tt.c :copy # => "Duplicate"
|
29
|
+
```
|
30
|
+
|
31
|
+
## Attributes
|
32
|
+
|
33
|
+
You probably know that `active_model` based classes have handy method **#human_attribute_name**. The main problems with it are the long method name and `humanization` on translation missing. The gem provides an **almost compatible equivalent #attr**.
|
34
|
+
|
35
|
+
```ruby
|
36
|
+
# en:
|
37
|
+
# attributes:
|
38
|
+
# base:
|
39
|
+
# email: "Email"
|
40
|
+
# user:
|
41
|
+
# email: "Login / Email"
|
42
|
+
|
43
|
+
# app/views/subscriptions/index.haml
|
44
|
+
# a base translation
|
45
|
+
tt.attr :email, :subscription # => "Email"
|
46
|
+
|
47
|
+
# app/views/users/index.haml
|
48
|
+
# a specific translation
|
49
|
+
tt.attr :email, :user # => "Login / Email"
|
50
|
+
|
51
|
+
# with 'current model reflection' (preferred): UsersController => :user
|
52
|
+
tt.attr :email # => "Login / Email"
|
53
|
+
```
|
54
|
+
|
55
|
+
`#attr` also looks into orm translations paths. If you place translations according to `active_record`:
|
56
|
+
|
57
|
+
```ruby
|
58
|
+
# en:
|
59
|
+
# activerecord:
|
60
|
+
# attributes:
|
61
|
+
# user:
|
62
|
+
# email: "Login / Email"
|
63
|
+
# attributes:
|
64
|
+
# user: "Email"
|
65
|
+
|
66
|
+
tt.c :email, :user # => "Login / Email"
|
67
|
+
```
|
68
|
+
For other `active_model` based orms please specify configuration in an initializator:
|
69
|
+
|
70
|
+
```ruby
|
71
|
+
# en:
|
72
|
+
# mongoid:
|
73
|
+
# attributes:
|
74
|
+
# user:
|
75
|
+
# email: "Login / Email"
|
76
|
+
# attributes:
|
77
|
+
# user: "Email"
|
78
|
+
|
79
|
+
# app/config/tt.rb
|
80
|
+
TT.config(prefix: :mongoid)
|
81
|
+
```
|
82
|
+
## Resources
|
83
|
+
|
84
|
+
There is another translation hard-to-use feature which are present in `active_model` - **.model_name.human**. It
|
85
|
+
returns a translated human name of a model. To get a plural version you should also provide the `count: 10` parameter.
|
86
|
+
With Dos-T you get two short methods which do the job - **#r** & **#rs** (human resource and resources):
|
87
|
+
|
88
|
+
```ruby
|
89
|
+
# en:
|
90
|
+
# models:
|
91
|
+
# user:
|
92
|
+
# one: "User"
|
93
|
+
# other: "Users"
|
94
|
+
# zero: "Userz"
|
95
|
+
|
96
|
+
tt.r :user # => "User"
|
97
|
+
tt.rs :user, 20 # => "Users"
|
98
|
+
tt.rs :user, 0 # => "Userz"
|
99
|
+
|
100
|
+
# app/views/users/show.haml
|
101
|
+
# with 'current model reflection' and the default value (10)
|
102
|
+
tt.rs # => "Users"
|
103
|
+
```
|
104
|
+
|
105
|
+
## Enums
|
106
|
+
|
107
|
+
Quite often a developer needs to provide a **human translation for the variants** of an enum attribute. Dos-T provides
|
108
|
+
a handy method **#enum**:
|
109
|
+
|
110
|
+
```ruby
|
111
|
+
# en:
|
112
|
+
# enums:
|
113
|
+
# base:
|
114
|
+
# role:
|
115
|
+
# a: "Admin"
|
116
|
+
# u: "User"
|
117
|
+
# subscription:
|
118
|
+
# role:
|
119
|
+
# c: "Subscriber"
|
120
|
+
|
121
|
+
# app/views/subscriptions/index.haml
|
122
|
+
tt.enum :role, :a, :user # => "Admin"
|
123
|
+
tt.enum :role, :c, :subscription # => "Subscriber"
|
124
|
+
|
125
|
+
# with 'current model reflection'
|
126
|
+
tt.enum :role, :u # => "User"
|
127
|
+
```
|
128
|
+
You can put enum translations inside orm translation namespace as with `#attr` method.
|
129
|
+
|
130
|
+
## Errors
|
131
|
+
|
132
|
+
Errors are an essential part of any application. Within `active_model` orms you have **errors.full_messages**
|
133
|
+
& **errors.full_messages_for** which are fine only for a few use-cases. To make it easier the gem has an
|
134
|
+
**almost compatible equivalent #e**:
|
135
|
+
|
136
|
+
```ruby
|
137
|
+
# en:
|
138
|
+
# errors:
|
139
|
+
# base:
|
140
|
+
# blank: "is blank"
|
141
|
+
# greater_than: "must be greater than %{count}"
|
142
|
+
# password:
|
143
|
+
# blank: "is empty"
|
144
|
+
# user:
|
145
|
+
# password:
|
146
|
+
# blank: "is invalid"
|
147
|
+
|
148
|
+
# app/views/users/index.haml
|
149
|
+
tt.e(:password, :blank, :user) # => "is invalid"
|
150
|
+
tt.e(:password, :greater_than, :user, count: 4) # => "must be greater than 4"
|
151
|
+
|
152
|
+
# with 'current model reflection'
|
153
|
+
tt.e(:password, :blank) # => "is invalid"
|
154
|
+
tt.e(:password, :greater_than, count: 4) # => "must be greater than 4"
|
155
|
+
|
156
|
+
tt.e(:password, :blank, :client) # => "is empty"
|
157
|
+
tt.e(:name, :blank, :client) # => "is blank"
|
158
|
+
```
|
159
|
+
|
160
|
+
## Actions
|
161
|
+
|
162
|
+
The unique feature of the gem which helps to reduce amount of duplications in flash messages, warnings and other messages
|
163
|
+
related to notification about some action on resources. To get the idea let's look into a typical rails controller:
|
164
|
+
|
165
|
+
```ruby
|
166
|
+
def create
|
167
|
+
if user.save
|
168
|
+
flash[:notice] = "The user has been created"
|
169
|
+
end
|
170
|
+
# other code
|
171
|
+
end
|
172
|
+
|
173
|
+
def update
|
174
|
+
if user.save
|
175
|
+
flash[:notice] = "The user has been updated"
|
176
|
+
end
|
177
|
+
# other code
|
178
|
+
end
|
179
|
+
|
180
|
+
def destroy
|
181
|
+
user.destroy
|
182
|
+
flash[:notice] = "The user has been deleted"
|
183
|
+
# other code
|
184
|
+
end
|
185
|
+
```
|
186
|
+
|
187
|
+
For one controller the situation looks fine, but a typical application has at least 10 to 15 controllers. As the result
|
188
|
+
these translations creates a long translation files with many duplications. To solve the problem method **#a**
|
189
|
+
comes on the scene:
|
190
|
+
|
191
|
+
```ruby
|
192
|
+
# en:
|
193
|
+
# actions:
|
194
|
+
# create:
|
195
|
+
# base: "The %{r} has been created"
|
196
|
+
# update:
|
197
|
+
# base: "The %{r} has been updated"
|
198
|
+
# delete:
|
199
|
+
# base: "The %{r} has been deleted"
|
200
|
+
# models:
|
201
|
+
# user:
|
202
|
+
# one: "User"
|
203
|
+
# other: "Users"
|
204
|
+
|
205
|
+
def create
|
206
|
+
if user.save
|
207
|
+
flash[:notice] = tt.a(:create, :user) # => "The user has been created"
|
208
|
+
end
|
209
|
+
# other code
|
210
|
+
end
|
211
|
+
|
212
|
+
def update
|
213
|
+
if user.save
|
214
|
+
flash[:notice] = tt.a(:update, :user) # => "The user has been updated"
|
215
|
+
end
|
216
|
+
# other code
|
217
|
+
end
|
218
|
+
|
219
|
+
def destroy
|
220
|
+
user.destroy
|
221
|
+
# with 'current model reflection'
|
222
|
+
flash[:notice] = tt.a(:delete) # => "The user has been deleted"
|
223
|
+
# other code
|
224
|
+
end
|
225
|
+
```
|
226
|
+
|
227
|
+
As you can see in translation you define a base translation for an action which expected at least the next variables:
|
228
|
+
- RS - `tt.rs`
|
229
|
+
- R - `tt.r`
|
230
|
+
- rs - `tt.rs.downcase`
|
231
|
+
- r - `tt.r.downcase`
|
232
|
+
|
233
|
+
If for some action one of the models should have a custom translation (a business logic, a grammar rule of a language)
|
234
|
+
just add a model name key in a action translations:
|
235
|
+
|
236
|
+
```ruby
|
237
|
+
# en:
|
238
|
+
# actions:
|
239
|
+
# create:
|
240
|
+
# base: "The %{r} has been created"
|
241
|
+
# photo: "The %{r} has been uploaded"
|
242
|
+
|
243
|
+
tt.a(:create, :user) # => "The user has been created"
|
244
|
+
tt.a(:create, :photo) # => "The photo has been uploaded"
|
245
|
+
```
|
246
|
+
Take a look at [examples folder](./examples/) for a more examples.
|
247
|
+
|
248
|
+
## Custom shortcuts
|
249
|
+
|
250
|
+
As an application grows `common` grows too. To avoid a long list of common words it's good to group them by group.
|
251
|
+
For example, words related to a user tips is good to place into `tips`, words related to forms into `forms`. To do it
|
252
|
+
the gem provides a configuration block:
|
253
|
+
|
254
|
+
```ruby
|
255
|
+
# app/config/tt.rb
|
256
|
+
TT.config do
|
257
|
+
lookup_key_method :tip, :tips
|
258
|
+
lookup_key_method :f, :forms
|
259
|
+
end
|
260
|
+
```
|
261
|
+
As the result your `tt` variable will have `#tip` & `#f` methods which behave like `#c`.
|
262
|
+
|
263
|
+
## Advanced usage & configuration
|
264
|
+
|
265
|
+
Dos-T was designed to be easy-as-possible for the default rails stack, but if you don't have it or don't have rails at
|
266
|
+
all it's not a problem. Let's look at a possible cases:
|
267
|
+
|
268
|
+
### You don't have ActionPack or want to use Dos-T outside the views
|
269
|
+
|
270
|
+
`tt` is an instance of `TT::Translator`. To create a variable you need `namespace` & `section` (optional) keys. In the rails
|
271
|
+
environment it's `controller_path` and `action_name`.
|
272
|
+
|
273
|
+
```ruby
|
274
|
+
class EmailApp < Sinatra::Base
|
275
|
+
before do
|
276
|
+
@tt = TT::Translator.new('emails')
|
277
|
+
end
|
278
|
+
|
279
|
+
post '/' do
|
280
|
+
# processing
|
281
|
+
{ status: tt.t(:handled) }.to_json # { status: I18n.t('emails.handled') }.to_json
|
282
|
+
end
|
283
|
+
end
|
284
|
+
|
285
|
+
class EmailSender
|
286
|
+
attr_reader :tt
|
287
|
+
|
288
|
+
def initialize
|
289
|
+
@tt = TT::Translator.new('services/email_sender')
|
290
|
+
end
|
291
|
+
|
292
|
+
def work
|
293
|
+
each_letter do |letter|
|
294
|
+
send_mail(subject: tt.a(:confirm, :email, user: letter.user_name))
|
295
|
+
# ...
|
296
|
+
end
|
297
|
+
end
|
298
|
+
end
|
299
|
+
```
|
300
|
+
|
301
|
+
### You use different to ActiveRecord orm
|
302
|
+
|
303
|
+
Just specify an orm i18n scope:
|
304
|
+
```ruby
|
305
|
+
# app/config/tt.rb
|
306
|
+
TT.config(prefix: :mongoid)
|
307
|
+
```
|
File without changes
|
data/lib/t_t.rb
CHANGED
@@ -1,65 +1,185 @@
|
|
1
|
+
require "active_support/inflector"
|
1
2
|
require "active_support/lazy_load_hooks"
|
3
|
+
require "active_support/multibyte/chars"
|
2
4
|
require "i18n"
|
3
5
|
|
4
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 = :base)
|
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
|
+
defaults << :"#{ type }.#{ base_suffix }"
|
32
|
+
|
33
|
+
[root, defaults]
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def prefix_lookup(prefix, base_suffix)
|
38
|
+
lambda do |ns, type|
|
39
|
+
parts = to_parts(ns)
|
40
|
+
model_path = parts.join('.')
|
41
|
+
|
42
|
+
root = "#{ prefix }.#{ type }.#{ model_path }"
|
43
|
+
defaults = [:"#{ type }.#{ model_path }"]
|
44
|
+
|
45
|
+
if parts.length > 1
|
46
|
+
pure_model = parts.last
|
47
|
+
defaults << :"#{ prefix }.#{ type }.#{ pure_model }"
|
48
|
+
defaults << :"#{ type }.#{ pure_model }"
|
49
|
+
end
|
50
|
+
defaults << :"#{ prefix }.#{ type }.#{ base_suffix }"
|
51
|
+
defaults << :"#{ type }.#{ base_suffix }"
|
52
|
+
|
53
|
+
[root, defaults]
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
5
58
|
class Translator
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
{
|
59
|
+
class << self
|
60
|
+
def lookup_key_method(meth_name, path)
|
61
|
+
class_eval <<-RUBY
|
62
|
+
def #{ meth_name }(key, options = {})
|
63
|
+
I18n.t "\#{ _config.fetch(:ns) }.#{ path }.\#{ key }",
|
64
|
+
{ default: [:"#{ path }.\#{ key }"] }.merge(options)
|
65
|
+
end
|
66
|
+
RUBY
|
67
|
+
end
|
68
|
+
|
69
|
+
def settings(custom = nil)
|
70
|
+
@settings ||= {}
|
71
|
+
|
72
|
+
if custom
|
73
|
+
unknown = custom.keys.detect { |key| ![:downcase, :prefix].include?(key) }
|
74
|
+
if unknown
|
75
|
+
raise ArgumentError, "TT doesn't know `#{ unknown }` option in the configuration"
|
76
|
+
else
|
77
|
+
@settings.merge!(custom)
|
78
|
+
end
|
11
79
|
end
|
12
|
-
|
80
|
+
|
81
|
+
@settings
|
82
|
+
end
|
13
83
|
end
|
14
84
|
|
15
|
-
|
16
|
-
|
17
|
-
|
85
|
+
lookup_key_method :c, :common
|
86
|
+
|
87
|
+
def initialize(ns, section = nil)
|
88
|
+
@lookup = Utils.lookup(self.class.settings[:prefix])
|
89
|
+
@err_lookup = Utils.lookup(self.class.settings[:prefix], :messages)
|
90
|
+
|
91
|
+
ns = Utils.to_parts(ns).join('.')
|
92
|
+
@config = { ns: ns, root: (section ? "#{ ns }.#{ section }" : ns) }
|
93
|
+
default_model = ns.to_s.singularize
|
94
|
+
[:actions, :attributes, :enums, :models].each do |i|
|
95
|
+
@config[i] = @lookup.call(default_model, i)
|
96
|
+
end
|
97
|
+
@config[:errors] = @err_lookup.call(default_model, :errors)
|
98
|
+
|
99
|
+
@downcase = self.class.settings.fetch(:downcase, Utils::DOWNCASE)
|
18
100
|
end
|
19
101
|
|
20
|
-
def
|
21
|
-
|
22
|
-
|
102
|
+
def a(name, model_name = nil, custom = {})
|
103
|
+
path, defaults = _resolve(model_name, :actions, name)
|
104
|
+
|
105
|
+
resource = r(model_name)
|
106
|
+
resources = rs(model_name)
|
107
|
+
I18n.t path, {
|
108
|
+
default: defaults, r: @downcase.call(resource, I18n.locale), R: resource,
|
109
|
+
rs: @downcase.call(resources, I18n.locale), RS: resources
|
110
|
+
}.merge!(custom)
|
23
111
|
end
|
24
112
|
|
25
|
-
|
26
|
-
|
113
|
+
def attr(name, model_name = nil)
|
114
|
+
path, defaults = _resolve(model_name, :attributes, name)
|
115
|
+
I18n.t path, default: defaults
|
116
|
+
end
|
27
117
|
|
28
|
-
def
|
29
|
-
|
118
|
+
def enum(name, kind, model_name = nil)
|
119
|
+
path, defaults = _resolve(model_name, :enums, "#{ name }.#{ kind }")
|
120
|
+
I18n.t path, default: defaults
|
30
121
|
end
|
31
122
|
|
32
|
-
def
|
33
|
-
|
123
|
+
def e(attr_name, error_name, *args)
|
124
|
+
custom = args.last.is_a?(Hash) ? args.pop : {}
|
125
|
+
model_name = args.first
|
126
|
+
path, defaults = _resolve_errors(model_name, attr_name, error_name)
|
127
|
+
I18n.t path, { default: defaults }.merge!(custom)
|
34
128
|
end
|
35
129
|
|
36
|
-
def
|
37
|
-
|
130
|
+
def r(model_name = nil)
|
131
|
+
rs(model_name, 1)
|
38
132
|
end
|
39
|
-
alias_method :record, :resource
|
40
133
|
|
41
|
-
def
|
42
|
-
|
134
|
+
def rs(model_name = nil, count = 10)
|
135
|
+
path, defaults = _resolve(model_name, :models)
|
136
|
+
I18n.t path, default: defaults, count: count
|
43
137
|
end
|
44
|
-
alias_method :records, :resources
|
45
138
|
|
46
|
-
def
|
47
|
-
|
139
|
+
def t(key, custom = {})
|
140
|
+
defaults = [:"#{ _config.fetch(:ns) }.common.#{ key }"].concat(Array(custom[:default]))
|
141
|
+
I18n.t "#{ _config.fetch(:root) }.#{ key }", custom.merge(default: defaults)
|
48
142
|
end
|
49
|
-
alias_method :no_records, :no_resources
|
50
143
|
|
51
144
|
private
|
52
145
|
|
53
|
-
|
146
|
+
def _config
|
147
|
+
@config
|
148
|
+
end
|
54
149
|
|
55
|
-
def
|
56
|
-
|
150
|
+
def _resolve(model_name, type, key = nil)
|
151
|
+
_resolve_with_lookup(@lookup, model_name, type, key)
|
152
|
+
end
|
57
153
|
|
58
|
-
|
154
|
+
def _resolve_errors(model_name, attr_name, error_name)
|
155
|
+
if attr_name == :base
|
156
|
+
_resolve_with_lookup(@err_lookup, model_name, :errors, error_name)
|
157
|
+
else
|
158
|
+
path, _defaults = _resolve(model_name, :errors, "#{ attr_name }.#{ error_name }")
|
159
|
+
defaults = _defaults + ["errors.messages.#{ error_name }".to_sym]
|
160
|
+
return path, defaults
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
164
|
+
def _resolve_with_lookup(lookup, model_name, type, key)
|
165
|
+
paths = model_name ? lookup.call(model_name, type) : _config.fetch(type)
|
166
|
+
if key
|
167
|
+
return "#{ paths.first }.#{ key }", paths.last.map { |i| :"#{ i }.#{ key }" }
|
168
|
+
else
|
169
|
+
return *paths
|
170
|
+
end
|
59
171
|
end
|
60
172
|
|
61
173
|
ActiveSupport.run_load_hooks(:tt, self)
|
62
174
|
end
|
175
|
+
|
176
|
+
def self.fork(&block)
|
177
|
+
Class.new(Translator, &block)
|
178
|
+
end
|
179
|
+
|
180
|
+
def self.config(&block)
|
181
|
+
Translator.instance_exec(&block)
|
182
|
+
end
|
63
183
|
end
|
64
184
|
|
65
185
|
if defined?(ActionPack) || defined?(ActionMailer)
|
@@ -68,12 +188,13 @@ if defined?(ActionPack) || defined?(ActionMailer)
|
|
68
188
|
|
69
189
|
included do
|
70
190
|
helper_method :tt
|
191
|
+
|
192
|
+
prepend_before_filter { instance_variable_set(:@tt, ::TT::Translator.new(controller_path, action_name)) }
|
71
193
|
end
|
72
194
|
|
73
195
|
private
|
74
196
|
|
75
197
|
def tt(*args)
|
76
|
-
@tt ||= ::TT::Translator.new(controller_path.gsub('/', '.'), action_name)
|
77
198
|
args.empty? ? @tt : @tt.t(*args)
|
78
199
|
end
|
79
200
|
end
|
@@ -86,3 +207,9 @@ if defined?(ActionPack) || defined?(ActionMailer)
|
|
86
207
|
include ::TT::Helper
|
87
208
|
end
|
88
209
|
end
|
210
|
+
|
211
|
+
if defined?(ActiveRecord)
|
212
|
+
ActiveSupport.on_load(:active_record) do
|
213
|
+
TT.config(prefix: :activerecord)
|
214
|
+
end
|
215
|
+
end
|
data/readme.md
ADDED
@@ -0,0 +1,96 @@
|
|
1
|
+
# Dos-T
|
2
|
+
|
3
|
+
[![Build Status](https://travis-ci.org/jalkoby/tt.svg?branch=master)](https://travis-ci.org/jalkoby/tt)
|
4
|
+
[![Gem Version](https://badge.fury.io/rb/t_t.svg)](https://badge.fury.io/rb/t_t)
|
5
|
+
|
6
|
+
Dos-T introduces a translation convention for a ruby web application (with a focus on the rails flow). The library is based on the next ideas:
|
7
|
+
- focus on a every day issues
|
8
|
+
- reduce amount of duplications inside translation files
|
9
|
+
- easy to use from day one & minimum to write (nobody likes to write a long and obvious method names)
|
10
|
+
- have a clear defaults
|
11
|
+
|
12
|
+
## Usage
|
13
|
+
|
14
|
+
Dos-T adds an extra helper method `tt` into your controllers, mailers & views. The best way to explain all features
|
15
|
+
is to look at [Cheatsheet](./cheatsheet.md). The below is shown a brief overview:
|
16
|
+
|
17
|
+
```Haml
|
18
|
+
# en:
|
19
|
+
# actions:
|
20
|
+
# add:
|
21
|
+
# base: "Add a new %{r}"
|
22
|
+
# attributes:
|
23
|
+
# user:
|
24
|
+
# name: "Name"
|
25
|
+
# email: "Email"
|
26
|
+
# role: "Role"
|
27
|
+
# common:
|
28
|
+
# actions: "Actions"
|
29
|
+
# confirm: "Are you sure?"
|
30
|
+
# edit: "Edit"
|
31
|
+
# delete: "Delete"
|
32
|
+
# enums:
|
33
|
+
# user:
|
34
|
+
# role:
|
35
|
+
# a: "Admin"
|
36
|
+
# g: "Guest"
|
37
|
+
# m: "Manager"
|
38
|
+
# models:
|
39
|
+
# user:
|
40
|
+
# one: "User"
|
41
|
+
# other: "Users"
|
42
|
+
|
43
|
+
# app/views/users/index.haml
|
44
|
+
%h2= tt.rs :user
|
45
|
+
|
46
|
+
%table
|
47
|
+
%thead
|
48
|
+
%th= tt.attr :name
|
49
|
+
%th= tt.attr :email
|
50
|
+
%th= tt.attr :role
|
51
|
+
%th= tt.c :actions
|
52
|
+
|
53
|
+
%tbody
|
54
|
+
- @users.each do |user|
|
55
|
+
%tr
|
56
|
+
%td= user.name
|
57
|
+
%td= user.email
|
58
|
+
%td= tt.enum :role, user.role
|
59
|
+
%td
|
60
|
+
= link_to tt.c(:edit), edit_user_path(user)
|
61
|
+
= link_to tt.c(:delete), user_path(user), method: :delete, confirm: tt.c(:confirm)
|
62
|
+
|
63
|
+
= link_to tt.a(:add), new_user_path
|
64
|
+
```
|
65
|
+
|
66
|
+
The result will be the next:
|
67
|
+
```Haml
|
68
|
+
%h2 Users
|
69
|
+
|
70
|
+
%table
|
71
|
+
%thead
|
72
|
+
%th Name
|
73
|
+
%th Email
|
74
|
+
%th Role
|
75
|
+
%th Actions
|
76
|
+
|
77
|
+
%tbody
|
78
|
+
- @users.each do |user|
|
79
|
+
%tr
|
80
|
+
%td= user.name
|
81
|
+
%td= user.email
|
82
|
+
%td= { 'a' => 'Admin', 'g' => 'Guest', 'm' => 'Manager' }[user.role]
|
83
|
+
%td
|
84
|
+
= link_to 'Edit', edit_user_path(user)
|
85
|
+
= link_to 'Delete', user_path(user), method: :delete, confirm: 'Are you sure?'
|
86
|
+
|
87
|
+
= link_to 'Add a new user', new_user_path
|
88
|
+
```
|
89
|
+
|
90
|
+
## Setup
|
91
|
+
|
92
|
+
Just add `gem "t_t"` into your Gemfile and run `bundle`.
|
93
|
+
|
94
|
+
## Requirements
|
95
|
+
|
96
|
+
Dos-T is tested against Ruby 1.9.3+. If your application uses Ruby on Rails the framework version should be 3.2+
|
data/t_t.gemspec
CHANGED
@@ -7,6 +7,7 @@ end
|
|
7
7
|
describe "ActionPack integration" do
|
8
8
|
before do
|
9
9
|
@controller = TTController.new
|
10
|
+
@controller.run_callbacks(:process_action)
|
10
11
|
end
|
11
12
|
|
12
13
|
it 'returns tt instance if the method was called without args' do
|
@@ -14,6 +15,7 @@ describe "ActionPack integration" do
|
|
14
15
|
end
|
15
16
|
|
16
17
|
it 'calls #t if args were passed' do
|
18
|
+
load_i18n(common: { tar: 'global_tar' })
|
17
19
|
assert_equal @controller.tt.c(:tar), 'global_tar'
|
18
20
|
end
|
19
21
|
end
|
@@ -0,0 +1,162 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
describe 'Methods related to models' do
|
4
|
+
before do
|
5
|
+
@tt = ARTranslator.new("admin/users", "spec")
|
6
|
+
end
|
7
|
+
|
8
|
+
describe 'actions' do
|
9
|
+
before do
|
10
|
+
load_i18n({
|
11
|
+
actions: {
|
12
|
+
base: { do: 'Do', make: 'Do', execute: 'Do' },
|
13
|
+
user: {
|
14
|
+
make: 'Make', execute: 'Make',
|
15
|
+
subscribe: 'The %{r} will be subscribed with other %{rs} of this day. %{RS} get 10% off with the subscription.'
|
16
|
+
},
|
17
|
+
admin: { user: { execute: 'Execute' } }
|
18
|
+
},
|
19
|
+
models: { admin: { user: { one: "Admin", other: "Admins" } } }
|
20
|
+
})
|
21
|
+
end
|
22
|
+
|
23
|
+
it 'returns a specific action' do
|
24
|
+
assert_equal @tt.a(:execute), 'Execute'
|
25
|
+
end
|
26
|
+
|
27
|
+
it 'returns a pure model action' do
|
28
|
+
assert_equal @tt.a(:make), 'Make'
|
29
|
+
end
|
30
|
+
|
31
|
+
it 'returns a base action' do
|
32
|
+
assert_equal @tt.a(:do), 'Do'
|
33
|
+
end
|
34
|
+
|
35
|
+
it 'allows to specify a custom model' do
|
36
|
+
assert_equal @tt.a(:execute, :user), 'Make'
|
37
|
+
end
|
38
|
+
|
39
|
+
it 'passes translations of a model' do
|
40
|
+
assert_equal @tt.a(:subscribe), 'The admin will be subscribed with other admins of this day. Admins get 10% off with the subscription.'
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
describe 'attributes' do
|
45
|
+
before do
|
46
|
+
load_i18n(attributes: {
|
47
|
+
base: { name: 'Name', phone: 'Phone', email: 'Email' },
|
48
|
+
user: { name: 'Nick', phone: 'Notification phone' },
|
49
|
+
admin: { user: { name: 'Contact admin name' } }
|
50
|
+
})
|
51
|
+
end
|
52
|
+
|
53
|
+
it 'returns a specific attribute' do
|
54
|
+
assert_equal @tt.attr(:name), 'Contact admin name'
|
55
|
+
end
|
56
|
+
|
57
|
+
it 'returns a pure model attribute' do
|
58
|
+
assert_equal @tt.attr(:phone), 'Notification phone'
|
59
|
+
end
|
60
|
+
|
61
|
+
it 'returns a base attribute' do
|
62
|
+
assert_equal @tt.attr(:email), 'Email'
|
63
|
+
end
|
64
|
+
|
65
|
+
it 'allows to specify a custom model' do
|
66
|
+
assert_equal @tt.attr(:name, :user), 'Nick'
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
describe 'enums' do
|
71
|
+
before do
|
72
|
+
load_i18n(enums: {
|
73
|
+
base: { gender: { f: 'Female', m: 'Male', o: 'Other' } },
|
74
|
+
user: { gender: { f: 'Woman', m: 'Man' } },
|
75
|
+
admin: { user: { gender: { f: 'Admin' } } }
|
76
|
+
})
|
77
|
+
end
|
78
|
+
|
79
|
+
it 'returns a specific enum' do
|
80
|
+
assert_equal @tt.enum(:gender, :f), 'Admin'
|
81
|
+
end
|
82
|
+
|
83
|
+
it 'returns a pure model enum' do
|
84
|
+
assert_equal @tt.enum(:gender, :m), 'Man'
|
85
|
+
end
|
86
|
+
|
87
|
+
it 'returns a base enum' do
|
88
|
+
assert_equal @tt.enum(:gender, :o), 'Other'
|
89
|
+
end
|
90
|
+
|
91
|
+
it 'allows to specify a custom model' do
|
92
|
+
assert_equal @tt.enum(:gender, :f, :user), 'Woman'
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
describe 'errors' do
|
97
|
+
before do
|
98
|
+
load_i18n(errors: {
|
99
|
+
messages: { name: { blank: "should be filled" }, fields_missed: "not all required fields are filled" },
|
100
|
+
user: { password: { weak: "Your password is weak" }, limit: 'The limit has been reached', duplicated: "The site has an account with the inputed information" },
|
101
|
+
admin: { user: { email: { empty: "The admin email should be filled" }, limit: "The system has reached the admin limit" } }
|
102
|
+
})
|
103
|
+
end
|
104
|
+
|
105
|
+
it 'returns a specific error' do
|
106
|
+
assert_equal @tt.e(:email, :empty), 'The admin email should be filled'
|
107
|
+
end
|
108
|
+
|
109
|
+
it 'returns a specific base error' do
|
110
|
+
assert_equal @tt.e(:base, :limit), 'The system has reached the admin limit'
|
111
|
+
end
|
112
|
+
|
113
|
+
it 'returns a pure model error' do
|
114
|
+
assert_equal @tt.e(:password, :weak), 'Your password is weak'
|
115
|
+
end
|
116
|
+
|
117
|
+
it 'returns a base pure model error' do
|
118
|
+
assert_equal @tt.e(:base, :duplicated), 'The site has an account with the inputed information'
|
119
|
+
end
|
120
|
+
|
121
|
+
it 'returns a base attribute error' do
|
122
|
+
assert_equal @tt.e(:name, :blank), 'should be filled'
|
123
|
+
end
|
124
|
+
|
125
|
+
it 'returns a base error' do
|
126
|
+
assert_equal @tt.e(:base, :fields_missed), 'not all required fields are filled'
|
127
|
+
assert_equal @tt.e(:roo, :fields_missed), 'not all required fields are filled'
|
128
|
+
end
|
129
|
+
|
130
|
+
it 'allows to specify a custom model' do
|
131
|
+
assert_equal @tt.e(:base, :limit, :user), 'The limit has been reached'
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
describe 'resource names' do
|
136
|
+
before do
|
137
|
+
@tt = ARTranslator.new('public/people')
|
138
|
+
load_i18n({
|
139
|
+
models: { person: { one: "whatever", other: "whatever" }, user: { one: "User", other: "Users" } },
|
140
|
+
activerecord: { models: {
|
141
|
+
public: { person: { one: "Celebrity", other: "Celebrities" } },
|
142
|
+
person: { one: "Person", other: "People" }
|
143
|
+
} }
|
144
|
+
})
|
145
|
+
end
|
146
|
+
|
147
|
+
it 'handles namespaces' do
|
148
|
+
assert_equal @tt.r, 'Celebrity'
|
149
|
+
assert_equal @tt.rs, 'Celebrities'
|
150
|
+
end
|
151
|
+
|
152
|
+
it 'looks inside orm namespace' do
|
153
|
+
assert_equal @tt.r(:person), 'Person'
|
154
|
+
assert_equal @tt.rs(:person), 'People'
|
155
|
+
end
|
156
|
+
|
157
|
+
it "fallbacks into base if orm namespace doesn't have a key" do
|
158
|
+
assert_equal @tt.r(:user), 'User'
|
159
|
+
assert_equal @tt.rs(:user), 'Users'
|
160
|
+
end
|
161
|
+
end
|
162
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
describe 'methods related to views' do
|
4
|
+
before do
|
5
|
+
@tt = ViewTranslator.new("tt", "spec")
|
6
|
+
end
|
7
|
+
|
8
|
+
describe '#t' do
|
9
|
+
before do
|
10
|
+
load_i18n(tt: {
|
11
|
+
common: { too: "namespace_too" },
|
12
|
+
spec: { foo: "spec_foo" }
|
13
|
+
})
|
14
|
+
end
|
15
|
+
|
16
|
+
it "looks for a section translation first" do
|
17
|
+
assert_equal @tt.t(:foo), "spec_foo"
|
18
|
+
end
|
19
|
+
|
20
|
+
it "looks into the section commons" do
|
21
|
+
assert_equal @tt.t(:too), "namespace_too"
|
22
|
+
end
|
23
|
+
|
24
|
+
it "allows a custom options" do
|
25
|
+
assert_equal @tt.t(:tar, default: "default_tar"), "default_tar"
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
describe '#c' do
|
30
|
+
before do
|
31
|
+
load_i18n(
|
32
|
+
common: { tar: 'global_tar' },
|
33
|
+
tt: { common: { foo: "namespace_foo" }, }
|
34
|
+
)
|
35
|
+
end
|
36
|
+
|
37
|
+
it "looks for a namespace translation first" do
|
38
|
+
assert_equal @tt.c(:foo), "namespace_foo"
|
39
|
+
end
|
40
|
+
|
41
|
+
it "falls back to a global translation" do
|
42
|
+
assert_equal @tt.c(:tar), "global_tar"
|
43
|
+
end
|
44
|
+
|
45
|
+
it "allows a custom options" do
|
46
|
+
assert_equal @tt.c(:car, default: "default_car"), 'default_car'
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
describe '#f' do
|
51
|
+
before do
|
52
|
+
load_i18n(
|
53
|
+
form: { edit: "global_edit", save: "global_save" },
|
54
|
+
tt: { form: { edit: "namespace_edit" } }
|
55
|
+
)
|
56
|
+
end
|
57
|
+
|
58
|
+
it "looks for a namespace translation first" do
|
59
|
+
assert_equal @tt.f(:edit), "namespace_edit"
|
60
|
+
end
|
61
|
+
|
62
|
+
it "falls back to a global translation" do
|
63
|
+
assert_equal @tt.f(:save), "global_save"
|
64
|
+
end
|
65
|
+
|
66
|
+
it "allows a custom options" do
|
67
|
+
assert_equal @tt.f(:commit, default: "default_commit"), "default_commit"
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
data/tests/test_helper.rb
CHANGED
@@ -4,39 +4,22 @@ require "rack/test"
|
|
4
4
|
require "action_controller"
|
5
5
|
require "t_t"
|
6
6
|
|
7
|
+
ViewTranslator = TT.fork do
|
8
|
+
lookup_key_method :f, :form
|
9
|
+
end
|
10
|
+
|
11
|
+
ARTranslator = TT.fork do
|
12
|
+
settings prefix: :activerecord
|
13
|
+
end
|
14
|
+
|
7
15
|
I18n.backend = I18n::Backend::Simple.new
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
save: "global_save"
|
19
|
-
},
|
20
|
-
tooltip: {
|
21
|
-
info: "global_info",
|
22
|
-
notice: "global_notice"
|
23
|
-
},
|
24
|
-
tt: {
|
25
|
-
common: {
|
26
|
-
foo: "namespace_foo",
|
27
|
-
bar: "namespace_bar"
|
28
|
-
},
|
29
|
-
crumbs: {
|
30
|
-
new: 'namespace_new'
|
31
|
-
},
|
32
|
-
form: {
|
33
|
-
edit: "namespace_edit"
|
34
|
-
},
|
35
|
-
spec: {
|
36
|
-
foo: "spec_foo"
|
37
|
-
},
|
38
|
-
tooltip: {
|
39
|
-
info: "namespace_info"
|
40
|
-
}
|
41
|
-
}
|
42
|
-
})
|
16
|
+
|
17
|
+
class Minitest::Spec
|
18
|
+
after :each do
|
19
|
+
I18n.backend.reload!
|
20
|
+
end
|
21
|
+
|
22
|
+
def load_i18n(data)
|
23
|
+
I18n.backend.store_translations(:en, data)
|
24
|
+
end
|
25
|
+
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: 0.0
|
4
|
+
version: 1.0.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:
|
11
|
+
date: 2016-02-15 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: i18n
|
@@ -116,14 +116,22 @@ extensions: []
|
|
116
116
|
extra_rdoc_files: []
|
117
117
|
files:
|
118
118
|
- ".gitignore"
|
119
|
+
- ".travis.yml"
|
119
120
|
- Gemfile
|
120
|
-
- LICENSE.txt
|
121
|
-
- README.md
|
122
121
|
- Rakefile
|
122
|
+
- cheatsheet.md
|
123
|
+
- examples/simple_app.yml
|
124
|
+
- gemfiles/Gemfile.actionpack-3.1.x
|
125
|
+
- gemfiles/Gemfile.actionpack-3.2.x
|
126
|
+
- gemfiles/Gemfile.actionpack-4.0.x
|
127
|
+
- gemfiles/Gemfile.actionpack-4.1.x
|
128
|
+
- gemfiles/Gemfile.actionpack-4.2.x
|
123
129
|
- lib/t_t.rb
|
130
|
+
- readme.md
|
124
131
|
- t_t.gemspec
|
125
132
|
- tests/lib/action_pack_test.rb
|
126
|
-
- tests/lib/
|
133
|
+
- tests/lib/model_test.rb
|
134
|
+
- tests/lib/view_test.rb
|
127
135
|
- tests/test_helper.rb
|
128
136
|
homepage: ''
|
129
137
|
licenses:
|
@@ -145,8 +153,9 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
145
153
|
version: '0'
|
146
154
|
requirements: []
|
147
155
|
rubyforge_project:
|
148
|
-
rubygems_version: 2.
|
156
|
+
rubygems_version: 2.4.5
|
149
157
|
signing_key:
|
150
158
|
specification_version: 4
|
151
159
|
summary: An opinioned I18n helper
|
152
160
|
test_files: []
|
161
|
+
has_rdoc:
|
data/LICENSE.txt
DELETED
@@ -1,22 +0,0 @@
|
|
1
|
-
Copyright (c) 2015 Sergey Pchelintsev
|
2
|
-
|
3
|
-
MIT License
|
4
|
-
|
5
|
-
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
-
a copy of this software and associated documentation files (the
|
7
|
-
"Software"), to deal in the Software without restriction, including
|
8
|
-
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
-
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
-
permit persons to whom the Software is furnished to do so, subject to
|
11
|
-
the following conditions:
|
12
|
-
|
13
|
-
The above copyright notice and this permission notice shall be
|
14
|
-
included in all copies or substantial portions of the Software.
|
15
|
-
|
16
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
-
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
-
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
-
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
-
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
-
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
-
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
DELETED
@@ -1,91 +0,0 @@
|
|
1
|
-
## About
|
2
|
-
|
3
|
-
The main goal of Dos-T is reducing time on supporting a multi-language web application by providing a few conventions and a short method names. We believe that it's a primary reason which slows down development. Cause it's boring to write a long path like `t('namespace.controller_name.action_name.section.key')`. Let's better spend time on something more interesting.
|
4
|
-
|
5
|
-
So what's makes Dos-T such a good tool(on our opinion):
|
6
|
-
|
7
|
-
### Example of page
|
8
|
-
|
9
|
-
```haml
|
10
|
-
/ users#index
|
11
|
-
= link_to tt.c(:new_resource, model: tt.resource), new_user_path, "data-tooltip" => tt(:new_user_tip)
|
12
|
-
|
13
|
-
%table
|
14
|
-
%tr
|
15
|
-
%th= tt.attr :name
|
16
|
-
%th= tt.attr :email
|
17
|
-
%th= tt.attr :phone
|
18
|
-
%th= tt.attr :role
|
19
|
-
%th= tt.c :actions
|
20
|
-
- @users.each do |user|
|
21
|
-
%tr
|
22
|
-
%td= user.name
|
23
|
-
%td= user.email
|
24
|
-
%td= user.phone
|
25
|
-
%td= tt.enum :role, user.role
|
26
|
-
%td
|
27
|
-
= link_to tt.f(:edit), edit_user_path(user)
|
28
|
-
= link_to tt.f(:delete), user_path(user), method: :delete, confirm: tt.c(:confirm)
|
29
|
-
|
30
|
-
/ tt.c looks in 'users.common', 'common'
|
31
|
-
/ tt.f looks in 'users.form', 'form'
|
32
|
-
/ tt looks in 'users.index', 'users.common'
|
33
|
-
/ tt.attr - calls human_attribute_name on User
|
34
|
-
/ tt.enum - calls human_attribute_name on User with role_%value%
|
35
|
-
/ tt.resource - User.model_name.human(count: 1)
|
36
|
-
```
|
37
|
-
|
38
|
-
### Global commons
|
39
|
-
|
40
|
-
`tt.c` - in every application there are some elements which are present in every page. For example, if a page has "delete some resource" button/link, it's good to have a confirmation message which tells a user "Are you sure?". At this point you add a key for this page, for example, in "admin.users.index.confirm" path and on page call `t('.confirm')`. Rails is smart enough to prepend a full path to ".confirm" key. But then you need to add confirmation on `admin/users#show` and you, probably, will not duplicate it for "show" page and move it into a common section. After that the translation helper can't help us and we need to provide a full path – `t('common.confirm')`. We reduce a duplication of translation keys, but have to write a long methods. Well, as always, programming is a compromise. But not this time, with Dos-T you can have both – just call `tt.c(:confirm)`. At first it will look into a section's common node(description of a section see below). If Dos-T doesn't find a translation there it will look into the global *common* >>> *common.confirm*. You, probably, are interested what's a section. By a section Dos-T means I18n path of the current controller(mailer). Let's review the previous example. If our current controller is `Admin::UsersController`, the section is *admin.users*. That's why if we want for deleting users a special confirmation we just add "admin.users.common.confirmation" key and it's done.
|
41
|
-
|
42
|
-
`tt.f` - the global `common` section could fast become a huge and you will add some namespaces into it. We prefer a facts over a supposes, but there is a 100% assumption that you will have this subsection - `common.form` with `save`, `edit`, `delete` and etc. Cause we don't know any application which doesn't have that. That's why Dos-T has another method `tt.f` which like `tt.c` - first looks into the current section(*controller_path.form*) and fallbacks into the global("form").
|
43
|
-
|
44
|
-
And finally, Dos-T allows you to define a new "common-used" sections. For example, for breadcrumbs. Just place "tt.rb" file in config/initializations that:
|
45
|
-
|
46
|
-
```ruby
|
47
|
-
ActiveSupport.on_load(:tt) do
|
48
|
-
shortcut(:crumbs, :breadcrumbs) # => tt.crumbs(:index) which looks into `breadcrumbs` sections
|
49
|
-
shortcut(:tip, :tooltips) # => tt.tip(:readonly) which looks into `tooltips` sections
|
50
|
-
end
|
51
|
-
```
|
52
|
-
|
53
|
-
### Section's commons
|
54
|
-
|
55
|
-
`tt` - let's back to our example with user management pages. Our clients decided to add a gravatar support. To make this feature more clear to admins we should put a tooltip with a description on index and show page, next to label "Gravatar". It brings a problem where to put a translation. If we put it into 'admin.users.index' we can use `t('.gravatar')` on index page, but have to use `t('admin.users.index.gravatar')` on show page and vice versa. And there Dos-T helps us. Just put `tt(:gravatar)` and put the tip into 'admin.users.common'. First it will act like a `t('.gravatar')` and fallbacks into section's common. And yes, `tt(:key)` is one symbol shorter than t('.key') – you can't avoid `t` in a favour to `tt`.
|
56
|
-
|
57
|
-
### Resource translations
|
58
|
-
|
59
|
-
`tt.attr` - we think every developer enjoys how `form_for` easily translates column's label - `f.label :column_name` - and Rails will magically translate it. But then we have to use an ugly way(`User.human_attribute_name(:column_name)`) to reuse these translations on index and show pages. It's become boring to type again and again this `human_attribute_name`. Dos-T provides a shortcut `tt.attr :column_name, User`. But it could be a shorter, cause if you call it in `UsersController` in 9/10 you closely work with User model. That's why, a second argument in `#attr` method is an optional and default value is a context class. A context class is computed on a controller name. If it's `users_controller`, Dos-T expects for a User model presence. If it's `Admin::UsersController` a context class is still User model.
|
60
|
-
|
61
|
-
### Other useful methods
|
62
|
-
|
63
|
-
* `tt.record([context_class])` – returns the singular human name of a model class(alias - resource)
|
64
|
-
* `tt.records([context_class])` – returns the plural human name of a model class(alias – resources)
|
65
|
-
* `tt.no_records([context_class])` – returns the zero form human name of a model class(alias – no_resources)
|
66
|
-
* `tt.enum(attr_name, variant, [context_class])` – returns a human variant name of an attribute. It looks for human attribute name of "%attr_name%_%variant%"
|
67
|
-
|
68
|
-
## Installation
|
69
|
-
|
70
|
-
Add this line to your application's Gemfile:
|
71
|
-
|
72
|
-
```ruby
|
73
|
-
gem 't_t'
|
74
|
-
```
|
75
|
-
|
76
|
-
And then execute:
|
77
|
-
|
78
|
-
$ bundle
|
79
|
-
|
80
|
-
Or install it yourself as:
|
81
|
-
|
82
|
-
$ gem install t_t
|
83
|
-
|
84
|
-
|
85
|
-
## Contributing
|
86
|
-
|
87
|
-
1. Fork it ( https://github.com/jalkoby/t_t/fork )
|
88
|
-
2. Create your feature branch (`git checkout -b my-new-feature`)
|
89
|
-
3. Commit your changes (`git commit -am 'Add some feature'`)
|
90
|
-
4. Push to the branch (`git push origin my-new-feature`)
|
91
|
-
5. Create a new Pull Request
|
data/tests/lib/t_t_test.rb
DELETED
@@ -1,146 +0,0 @@
|
|
1
|
-
require 'test_helper'
|
2
|
-
|
3
|
-
describe TT::Translator do
|
4
|
-
before do
|
5
|
-
@tt = TT::Translator.new("tt", "spec")
|
6
|
-
end
|
7
|
-
|
8
|
-
describe '#t' do
|
9
|
-
it "looks for a section translation first" do
|
10
|
-
assert_equal @tt.t(:foo), "spec_foo"
|
11
|
-
end
|
12
|
-
|
13
|
-
it "falls back to common namespace translations" do
|
14
|
-
assert_equal @tt.t(:bar), "namespace_bar"
|
15
|
-
end
|
16
|
-
|
17
|
-
it "allows a custom options" do
|
18
|
-
assert_equal @tt.t(:tar, default: "default_tar"), "default_tar"
|
19
|
-
end
|
20
|
-
end
|
21
|
-
|
22
|
-
describe '#c' do
|
23
|
-
it "looks for a namespace translation first" do
|
24
|
-
assert_equal @tt.c(:foo), "namespace_foo"
|
25
|
-
end
|
26
|
-
|
27
|
-
it "falls back to a global translation" do
|
28
|
-
assert_equal @tt.c(:tar), "global_tar"
|
29
|
-
end
|
30
|
-
|
31
|
-
it "allows a custom options" do
|
32
|
-
assert_equal @tt.c(:car, default: "default_car"), 'default_car'
|
33
|
-
end
|
34
|
-
end
|
35
|
-
|
36
|
-
describe '#f' do
|
37
|
-
it "looks for a namespace translation first" do
|
38
|
-
assert_equal @tt.f(:edit), "namespace_edit"
|
39
|
-
end
|
40
|
-
|
41
|
-
it "falls back to a global translation" do
|
42
|
-
assert_equal @tt.f(:save), "global_save"
|
43
|
-
end
|
44
|
-
|
45
|
-
it "allows a custom options" do
|
46
|
-
assert_equal @tt.f(:commit, default: "default_commit"), "default_commit"
|
47
|
-
end
|
48
|
-
end
|
49
|
-
|
50
|
-
describe 'a model related methods' do
|
51
|
-
before do
|
52
|
-
@klass = Minitest::Mock.new
|
53
|
-
end
|
54
|
-
|
55
|
-
describe '#attr' do
|
56
|
-
it 'uses the provided class' do
|
57
|
-
@klass.expect(:human_attribute_name, 'Nombre', [:name])
|
58
|
-
assert_equal @tt.attr(:name, @klass), 'Nombre'
|
59
|
-
end
|
60
|
-
|
61
|
-
it 'uses a context class by default' do
|
62
|
-
@klass.expect(:human_attribute_name, 'Nombre', [:name])
|
63
|
-
@tt.stub(:context_klass, @klass) do
|
64
|
-
assert_equal @tt.attr(:name), 'Nombre'
|
65
|
-
end
|
66
|
-
end
|
67
|
-
end
|
68
|
-
|
69
|
-
describe '#enum' do
|
70
|
-
it 'uses the provided class' do
|
71
|
-
@klass.expect(:human_attribute_name, 'Melody', ['type_melody'])
|
72
|
-
assert_equal @tt.enum(:type, :melody, @klass), 'Melody'
|
73
|
-
end
|
74
|
-
|
75
|
-
it 'uses a context class by default' do
|
76
|
-
@klass.expect(:human_attribute_name, 'Sound', ['type_sound'])
|
77
|
-
@tt.stub(:context_klass, @klass) do
|
78
|
-
assert_equal @tt.enum(:type, :sound), 'Sound'
|
79
|
-
end
|
80
|
-
end
|
81
|
-
end
|
82
|
-
|
83
|
-
describe 'a model name' do
|
84
|
-
before do
|
85
|
-
@model_name = Minitest::Mock.new
|
86
|
-
@klass.expect(:model_name, @model_name)
|
87
|
-
end
|
88
|
-
|
89
|
-
describe '#resource' do
|
90
|
-
before do
|
91
|
-
@model_name.expect(:human, 'Coche', [{ count: 1 }])
|
92
|
-
end
|
93
|
-
|
94
|
-
it 'uses the provided class' do
|
95
|
-
assert_equal @tt.resource(@klass), 'Coche'
|
96
|
-
end
|
97
|
-
|
98
|
-
it 'uses a context class by default' do
|
99
|
-
@tt.stub(:context_klass, @klass) do
|
100
|
-
assert_equal @tt.resource, 'Coche'
|
101
|
-
end
|
102
|
-
end
|
103
|
-
end
|
104
|
-
|
105
|
-
describe '#resources' do
|
106
|
-
before do
|
107
|
-
@model_name.expect(:human, 'Coches', [{ count: 10 }])
|
108
|
-
end
|
109
|
-
|
110
|
-
it 'uses the provided class' do
|
111
|
-
assert_equal @tt.resources(@klass), 'Coches'
|
112
|
-
end
|
113
|
-
|
114
|
-
it 'uses a context class by default' do
|
115
|
-
@tt.stub(:context_klass, @klass) do
|
116
|
-
assert_equal @tt.resources, 'Coches'
|
117
|
-
end
|
118
|
-
end
|
119
|
-
end
|
120
|
-
|
121
|
-
describe '#no_resources' do
|
122
|
-
before do
|
123
|
-
@model_name.expect(:human, 'Coches', [{ count: 0 }])
|
124
|
-
end
|
125
|
-
|
126
|
-
it 'uses the provided class' do
|
127
|
-
assert_equal @tt.no_resources(@klass), 'Coches'
|
128
|
-
end
|
129
|
-
|
130
|
-
it 'uses a context class by default' do
|
131
|
-
@tt.stub(:context_klass, @klass) do
|
132
|
-
assert_equal @tt.no_resources, 'Coches'
|
133
|
-
end
|
134
|
-
end
|
135
|
-
end
|
136
|
-
|
137
|
-
after do
|
138
|
-
assert @model_name.verify
|
139
|
-
end
|
140
|
-
end
|
141
|
-
|
142
|
-
after do
|
143
|
-
assert @klass.verify
|
144
|
-
end
|
145
|
-
end
|
146
|
-
end
|