t_t 0.0.1 → 1.0.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 +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
|
+
[](https://travis-ci.org/jalkoby/tt)
|
4
|
+
[](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
|