t_t 1.0.1 → 1.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Rakefile +6 -0
- data/cheatsheet.md +12 -12
- data/docs/action_factory.md +179 -0
- data/examples/simple_app.yml +8 -8
- data/lib/t_t/action_factory.rb +137 -0
- data/lib/t_t/builtin_rules.rb +45 -0
- data/lib/t_t.rb +32 -22
- data/readme.md +11 -2
- data/t_t.gemspec +1 -1
- data/tests/lib/action_factory_test.rb +60 -0
- data/tests/lib/builtin_rules_test.rb +52 -0
- data/tests/lib/model_test.rb +3 -3
- data/tests/test_helper.rb +13 -5
- metadata +7 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b73436334b6c70340cf56c69878f5645ddecbd12
|
4
|
+
data.tar.gz: 6f9f3913be2190fc19abce30109a1816621562b8
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a9c423a3ab34a17a0f4f858a773d7c0d60250e32f18679e469e136de30b22b68b5fe5af7e30820c83fbedbcd47d097a65807050989ec875422daa0ba577946ad
|
7
|
+
data.tar.gz: 42c316f2a5355a2b8379724847f8088582c3f46782811fff42595f6e49f7795ac711dfefe090d41f225db956c08507d02d4cb7a598bdfcbf900d9d393d787ab7
|
data/Rakefile
CHANGED
data/cheatsheet.md
CHANGED
@@ -84,8 +84,7 @@ You probably know that `active_model` based classes have handy method **#human_a
|
|
84
84
|
```ruby
|
85
85
|
# en:
|
86
86
|
# attributes:
|
87
|
-
#
|
88
|
-
# email: "Email"
|
87
|
+
# email: "Email"
|
89
88
|
# user:
|
90
89
|
# email: "Login / Email"
|
91
90
|
|
@@ -241,12 +240,10 @@ comes on the scene:
|
|
241
240
|
```ruby
|
242
241
|
# en:
|
243
242
|
# actions:
|
244
|
-
#
|
245
|
-
#
|
246
|
-
#
|
247
|
-
#
|
248
|
-
# delete:
|
249
|
-
# base: "The %{r} has been deleted"
|
243
|
+
# base:
|
244
|
+
# create: "The %{r} has been created"
|
245
|
+
# update: "The %{r} has been updated"
|
246
|
+
# delete: "The %{r} has been deleted"
|
250
247
|
# models:
|
251
248
|
# user:
|
252
249
|
# one: "User"
|
@@ -286,14 +283,17 @@ just add a model name key in a action translations:
|
|
286
283
|
```ruby
|
287
284
|
# en:
|
288
285
|
# actions:
|
289
|
-
#
|
290
|
-
#
|
291
|
-
#
|
286
|
+
# base:
|
287
|
+
# create: "The %{r} has been created"
|
288
|
+
# photo:
|
289
|
+
# create: "The %{r} has been uploaded"
|
292
290
|
|
293
291
|
tt.a(:create, :user) # => "The user has been created"
|
294
292
|
tt.a(:create, :photo) # => "The photo has been uploaded"
|
295
293
|
```
|
296
|
-
|
294
|
+
|
295
|
+
To simplify generation of translations Dos-T provides [the action factory](./docs/action_factory.md).
|
296
|
+
For more examples take a look [here](./examples/).
|
297
297
|
|
298
298
|
## Custom shortcuts
|
299
299
|
|
@@ -0,0 +1,179 @@
|
|
1
|
+
# Overview
|
2
|
+
|
3
|
+
Dos-T provides a factory (not required by default) to generate 'action'-translations in a few lines.
|
4
|
+
|
5
|
+
```ruby
|
6
|
+
# config/locales/actions.rb
|
7
|
+
require 't_t/action_factory'
|
8
|
+
|
9
|
+
TT.define_actions(:en, :de) do |f|
|
10
|
+
f.add :see_all, en: "See all %{rs}", de: "Siehe alle %{RS}"
|
11
|
+
end
|
12
|
+
```
|
13
|
+
|
14
|
+
From the example above you've understood the basic DSL's api, but started wondering what's a point of it due to fill
|
15
|
+
a yml file will be faster and easier. The reason comes when you will face a grammar rule which requires a different
|
16
|
+
texts. The most popular case is an English "a/an" rule. For example, an application has the next translation:
|
17
|
+
|
18
|
+
```ruby
|
19
|
+
# config/locales/actions.en.yml
|
20
|
+
en:
|
21
|
+
actions:
|
22
|
+
base:
|
23
|
+
choose: "Please, choose a %{r}"
|
24
|
+
agent:
|
25
|
+
choose: "Please, choose an %{r}"
|
26
|
+
article:
|
27
|
+
choose: "Please, choose an %{r}"
|
28
|
+
occupation:
|
29
|
+
choose: "Please, choose an %{r}"
|
30
|
+
#...
|
31
|
+
```
|
32
|
+
|
33
|
+
This grammar rule forces us to create n * m keys where:
|
34
|
+
- n is a count of actions with the rule
|
35
|
+
- m is a count of models which uses "an"
|
36
|
+
A plain translation file becomes large what makes the its maintenance hard. With Dos-T it's easy due to you
|
37
|
+
can teach DSL some grammar and it will generates all translation for you:
|
38
|
+
|
39
|
+
```ruby
|
40
|
+
# config/locales/actions.rb
|
41
|
+
require 't_t/action_factory'
|
42
|
+
|
43
|
+
TT.define_actions(:en) do |f|
|
44
|
+
f.for(:en) do |l|
|
45
|
+
# defines `a/an` rule for English where:
|
46
|
+
# base - a base action translation or a result of the previous rule processing
|
47
|
+
# a_meta - a action-related metadata (could be specified on adding an action)
|
48
|
+
# r_meta - a resource-related metadata (could be specified on marking a resource to use a rule)
|
49
|
+
l.rule(:an) { |base, a_meta, r_meta| a_meta }
|
50
|
+
|
51
|
+
# registers a resources which should use the rule
|
52
|
+
l.use_rule_for(:an, :agent, :article, occupation: "a useless resource (`occupation`) metadata for the `an` rule")
|
53
|
+
end
|
54
|
+
|
55
|
+
# "Please, choose an %{r}" is an action metadata for the rule
|
56
|
+
f.add :choose, en: f.with_rules("Please, choose a %{r}", an: "Please, choose an %{r}")
|
57
|
+
f.add :create, en: f.with_rules("Create a %{r}", an: "Create an %{r}")
|
58
|
+
f.add :delete_all, en: "Do you want to delete all %{rs}"
|
59
|
+
end
|
60
|
+
```
|
61
|
+
|
62
|
+
Here an another example with a more complex grammar rules:
|
63
|
+
|
64
|
+
```ruby
|
65
|
+
require 't_t/action_factory'
|
66
|
+
# de:
|
67
|
+
# models:
|
68
|
+
# article:
|
69
|
+
# one: "Artikel"
|
70
|
+
# other: "Artikel"
|
71
|
+
# child:
|
72
|
+
# one: "Kind"
|
73
|
+
# other: "Kinder"
|
74
|
+
# comment:
|
75
|
+
# one: "Komment"
|
76
|
+
# other: "Komments"
|
77
|
+
# list:
|
78
|
+
# one: "Liste"
|
79
|
+
# other: "Listen"
|
80
|
+
# log:
|
81
|
+
# one: "Protokoll"
|
82
|
+
# other: "Protokolle"
|
83
|
+
# person:
|
84
|
+
# one: "Person"
|
85
|
+
# other: "Personen"
|
86
|
+
# ru:
|
87
|
+
# models:
|
88
|
+
# article:
|
89
|
+
# one: "Статья"
|
90
|
+
# other: "Статьи"
|
91
|
+
# child:
|
92
|
+
# one: "Ребенок"
|
93
|
+
# other: "Дети"
|
94
|
+
# comment:
|
95
|
+
# one: "Комментарий"
|
96
|
+
# other: "Комментарии"
|
97
|
+
# list:
|
98
|
+
# one: "Список"
|
99
|
+
# other: "Списки"
|
100
|
+
# log:
|
101
|
+
# one: "Запись"
|
102
|
+
# other: "Записи"
|
103
|
+
# person:
|
104
|
+
# one: "Персона"
|
105
|
+
# other: "Персоны"
|
106
|
+
|
107
|
+
TT.define_actions(:de, :ru) do |f|
|
108
|
+
f.for(:de) do |l|
|
109
|
+
# German articles are sensitive to gender
|
110
|
+
l.rule(:feminine) { |_, a_meta, _| a_meta }
|
111
|
+
l.rule(:neuter) { |_, a_meta, _| a_meta }
|
112
|
+
|
113
|
+
l.use_rule_for(:feminine, :list, :person)
|
114
|
+
l.use_rule_for(:neuter, :child, :log)
|
115
|
+
end
|
116
|
+
|
117
|
+
f.for(:ru) do |l|
|
118
|
+
# Russian language has 6 noun cases(падежи)
|
119
|
+
# for passive voice Accuse case(Винительный падеж - кого? что?) is used
|
120
|
+
l.rule(:accuse) { |base, _, r_meta| r_meta.inject(base) { |str, (k, v)| str.gsub("%{#{ k }}", v) } }
|
121
|
+
l.use_rule_for :accuse,
|
122
|
+
article: { r: "статью", R: "Статью" }, # the plural version is not changing
|
123
|
+
child: { r: "ребенка", rs: "детей", R: "Ребенка", RS: "Детей" },
|
124
|
+
person: { r: "персону", rs: "персон", R: "Персону", RS: "Персон" }
|
125
|
+
end
|
126
|
+
|
127
|
+
f.add :add,
|
128
|
+
de: f.with_rules("Neuen %{R} hinzufügen", feminine: "Neue %{R} hinzufügen", neuter: "Neues %{R} hinzufügen"),
|
129
|
+
ru: f.with_rules("Добавить %{r}", :accuse)
|
130
|
+
|
131
|
+
f.add :edit,
|
132
|
+
de: f.with_rules("Den %{R} bearbeiten", feminine: "Die %{R} bearbeiten", neuter: "Das %{R} bearbeiten"),
|
133
|
+
ru: f.with_rules("Изменить %{r}", :accuse)
|
134
|
+
|
135
|
+
f.add :delete_all,
|
136
|
+
de: "Alle %{RS} löschen"
|
137
|
+
ru: f.with_rules("Удалить %{rs}", :accuse)
|
138
|
+
end
|
139
|
+
```
|
140
|
+
|
141
|
+
The generated list of actions is present in the following table:
|
142
|
+
|
143
|
+
||add-de|edit-de|delete-de|add-ru|edit-ru|delete-ru|
|
144
|
+
|---|---|---|---|---|---|---|
|
145
|
+
|article|Neuen Artikel hinzufügen|Den Artikel bearbeiten|Alle Artikel löschen|Добавить статью|Изменить статью|Удалить статьи|
|
146
|
+
|child|Neues Kind hinzufügen|Das Kind bearbeiten|Alle Kinder löschen|Добавить ребенка|Изменить ребенка|Удалить детей|
|
147
|
+
|comment|Neuen Komment hinzufügen|Den Komment bearbeiten|Alle Komments löschen|Добавить комментарий|Изменить комментарий|Удалить комментарии|
|
148
|
+
|list|Neue Liste hinzufügen|Die Liste bearbeiten|Alle Listen löschen|Добавить список|Изменить список|Удалить списки|
|
149
|
+
|log|Neues Protokoll hinzufügen|Das Protokoll bearbeiten|Alle Protokolle löschen|Добавить запись|Изменить запись|Удалить записи|
|
150
|
+
|person|Neue Person hinzufügen|Die Person bearbeiten|Alle Personen löschen|Добавить персону|Изменить персону|Удалить персон|
|
151
|
+
|
152
|
+
With the factory you get a flexibility in resource renaming - just change of a few lines. A possibility to make a typo
|
153
|
+
in a similar translations down to zero. All shown rules a included in the factory, but not activated.
|
154
|
+
To do it, do the next:
|
155
|
+
|
156
|
+
```ruby
|
157
|
+
TT.define_actions(:en, :de, :ru) do |f|
|
158
|
+
# the naming convention is similar to BEM
|
159
|
+
f.activate_rules(:en__an, :de__gender, :ru__accuse)
|
160
|
+
end
|
161
|
+
```
|
162
|
+
|
163
|
+
If you would like to add another grammar rule feel free to make a pull request.
|
164
|
+
|
165
|
+
## Adding an exceptions
|
166
|
+
|
167
|
+
Dos-T was built to maximize the usage of patterns in translations. It helps to avoid typos and long files, but texts
|
168
|
+
become more technical. In the previous section, you probably noticed that translations like "Das Kind bearbeiten"
|
169
|
+
(change the child) or "Изменить персону"(change the person) better to replace by more human oriented
|
170
|
+
"Das Kind-Profile bearbeiten" (change the child's profile) and "Изменить биографию персоны" (change the person's bio).
|
171
|
+
For those situations the library allows to specify an exceptions:
|
172
|
+
|
173
|
+
```ruby
|
174
|
+
TT.define_actions(:de, :ru) do |f|
|
175
|
+
# declaration of rules and actions
|
176
|
+
|
177
|
+
f.add_exception(:child, ru: { edit: "Das Kind-Profile bearbeiten" }, de: { edit: "Изменить профиль ребенка" })
|
178
|
+
end
|
179
|
+
```
|
data/examples/simple_app.yml
CHANGED
@@ -101,11 +101,11 @@ en:
|
|
101
101
|
blank: Select at least one task list
|
102
102
|
# action related
|
103
103
|
actions:
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
104
|
+
base:
|
105
|
+
choose_few: "Choose one or more %{rs}"
|
106
|
+
create: "The %{r} has been created"
|
107
|
+
select_before: "Please choose a %{r} before"
|
108
|
+
task:
|
109
|
+
create: "The %{r} hass been added"
|
110
|
+
icon:
|
111
|
+
select_before: "Please choose an %{r} before"
|
@@ -0,0 +1,137 @@
|
|
1
|
+
require 't_t/builtin_rules'
|
2
|
+
|
3
|
+
module TT
|
4
|
+
class ActionFactory
|
5
|
+
Action = Struct.new(:base, :rules)
|
6
|
+
Option = Struct.new(:key, :meta) do
|
7
|
+
def self.parse(list)
|
8
|
+
list.flat_map do |item|
|
9
|
+
item.respond_to?(:map) ? item.map { |key, meta| new(key, meta) } : new(item)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
class Locale
|
15
|
+
def initialize
|
16
|
+
@rules = {}
|
17
|
+
@list = {}
|
18
|
+
end
|
19
|
+
|
20
|
+
def rule(key, &block)
|
21
|
+
@rules[key] = block
|
22
|
+
@list[key] = []
|
23
|
+
end
|
24
|
+
|
25
|
+
def use_rule_for(key, *list)
|
26
|
+
@list[key].concat(Option.parse(list))
|
27
|
+
end
|
28
|
+
|
29
|
+
def knows_rule?(key)
|
30
|
+
@rules.has_key?(key)
|
31
|
+
end
|
32
|
+
|
33
|
+
def compile(action)
|
34
|
+
action.rules.inject(base: action.base) do |result, a_option|
|
35
|
+
rule = @rules.fetch(a_option.key)
|
36
|
+
|
37
|
+
@list.fetch(a_option.key).each do |r_option|
|
38
|
+
base = result.fetch(r_option.key, action.base)
|
39
|
+
result[r_option.key] = rule.call(base, a_option.meta, r_option.meta)
|
40
|
+
end
|
41
|
+
|
42
|
+
result
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def initialize(*locales)
|
48
|
+
@actions = {}
|
49
|
+
@locales = {}
|
50
|
+
@exceptions = {}
|
51
|
+
|
52
|
+
locales.each do |lkey|
|
53
|
+
@actions[lkey] = {}
|
54
|
+
@exceptions[lkey] = {}
|
55
|
+
@locales[lkey] = Locale.new
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def for(key, &block)
|
60
|
+
yield @locales.fetch(key) { raise_error "`#{ key }` is unknown" }
|
61
|
+
end
|
62
|
+
|
63
|
+
def activate_rules(*list)
|
64
|
+
list.each { |rkey| BuiltinRules.send(rkey, self) }
|
65
|
+
end
|
66
|
+
|
67
|
+
def add(akey, list)
|
68
|
+
@locales.each do |lkey, locale|
|
69
|
+
unless action = list[lkey]
|
70
|
+
raise_error "action `#{ akey }` is missing for `#{ lkey }` locale"
|
71
|
+
end
|
72
|
+
|
73
|
+
action = Action.new(action, []) if action.is_a?(String)
|
74
|
+
|
75
|
+
if action.is_a?(Action)
|
76
|
+
action.rules.each do |rule|
|
77
|
+
next if locale.knows_rule?(rule.key)
|
78
|
+
raise_error "`#{ rule.key }` is an unknown rule for `#{ lkey }` locale"
|
79
|
+
end
|
80
|
+
else
|
81
|
+
raise_error "the value of `#{ akey }` action for `#{ lkey }` locale has a wrong type"
|
82
|
+
end
|
83
|
+
|
84
|
+
@actions[lkey][akey] = action
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
def with_rules(base, *list)
|
89
|
+
Action.new(base, Option.parse(list))
|
90
|
+
end
|
91
|
+
|
92
|
+
def add_exception(mkey, schema)
|
93
|
+
schema.each do |lkey, list|
|
94
|
+
raise_error("`#{ lkey }` is an unknown locale") unless @locales.has_key?(lkey)
|
95
|
+
|
96
|
+
list.each do |akey, str|
|
97
|
+
unless @actions[lkey].has_key?(akey)
|
98
|
+
raise_error "`#{ akey }` action is not specified. Do it before add an exception"
|
99
|
+
end
|
100
|
+
|
101
|
+
@exceptions[lkey][akey] ||= {}
|
102
|
+
@exceptions[lkey][akey][mkey] = str
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
def as_hash
|
108
|
+
@actions.inject({}) do |hash, (lkey, list)|
|
109
|
+
locale = @locales.fetch(lkey)
|
110
|
+
|
111
|
+
actions = list.inject({}) do |result, (akey, action)|
|
112
|
+
keys = locale.compile(action).merge!(@exceptions[lkey].fetch(akey, {}))
|
113
|
+
keys.each do |mkey, str|
|
114
|
+
result[mkey] = {} unless result.has_key?(mkey)
|
115
|
+
result[mkey][akey] = str
|
116
|
+
end
|
117
|
+
|
118
|
+
result
|
119
|
+
end
|
120
|
+
|
121
|
+
hash.merge!(lkey => { actions: actions })
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
private
|
126
|
+
|
127
|
+
def raise_error(base)
|
128
|
+
raise ArgumentError, "t_t: #{ base }"
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
def self.define_actions(*args)
|
133
|
+
f = ActionFactory.new(*args)
|
134
|
+
yield f
|
135
|
+
f.as_hash
|
136
|
+
end
|
137
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
module TT
|
2
|
+
module BuiltinRules
|
3
|
+
extend self
|
4
|
+
|
5
|
+
# The indefinite article a (before a consonant sound) or an (before a vowel sound)
|
6
|
+
# is used only with singular, countable nouns.
|
7
|
+
def en__an(f)
|
8
|
+
f.for(:en) do |l|
|
9
|
+
l.rule(:an) { |_, a_meta, _| a_meta }
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
# The articles in German
|
14
|
+
# | masculine | feminine | neuter | plural |
|
15
|
+
# | neuen | neue | neues | neue |
|
16
|
+
# | keinen | keine | kein | keine |
|
17
|
+
# | der | die | das | die |
|
18
|
+
|
19
|
+
# this lambda generate default translation for masculine form
|
20
|
+
# & add exceptions for feminine & neuter genders
|
21
|
+
# ie
|
22
|
+
# new:
|
23
|
+
# base: "Neuen %{r} hinzufügen" -> "Neuen Benutzer anlegen"
|
24
|
+
# company: "Neues %{r} hinzufügen" -> "Neues Unternehmen anlegen"
|
25
|
+
# role: "Neue %{r} hinzufügen" -> "Neue Rolle hinzufügen"
|
26
|
+
def de__gender(f)
|
27
|
+
f.for(:de) do |l|
|
28
|
+
l.rule(:feminine) { |_, a_meta, _| a_meta }
|
29
|
+
l.rule(:neuter) { |_, a_meta, _| a_meta }
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
# To get a correct translation in Russian
|
34
|
+
# you need to set the proper ending for object by using `Винительный падеж - Кого? Что?`
|
35
|
+
# "Создать Компанию(кого?) & Cоздать Сектор(что?)"
|
36
|
+
# for `что?` we can use the resource name, for `кого?` - need to provide a separated key
|
37
|
+
def ru__accuse(f)
|
38
|
+
f.for(:ru) do |l|
|
39
|
+
l.rule(:accuse) do |base, _, r_meta|
|
40
|
+
r_meta.inject(base) { |str, (k, t)| str.gsub("%{#{k}}", t) }
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
data/lib/t_t.rb
CHANGED
@@ -9,7 +9,7 @@ module TT
|
|
9
9
|
|
10
10
|
DOWNCASE = lambda { |str, locale| (locale == :en) ? str.downcase : str.mb_chars.downcase.to_s }
|
11
11
|
|
12
|
-
def lookup(prefix, base_suffix
|
12
|
+
def lookup(prefix, base_suffix)
|
13
13
|
prefix ? prefix_lookup(prefix, base_suffix) : simple_lookup(base_suffix)
|
14
14
|
end
|
15
15
|
|
@@ -28,7 +28,11 @@ module TT
|
|
28
28
|
|
29
29
|
defaults = []
|
30
30
|
defaults << :"#{ type }.#{ parts.last }" if parts.length > 1
|
31
|
-
|
31
|
+
if base_suffix
|
32
|
+
defaults << :"#{ type }.#{ base_suffix }"
|
33
|
+
else
|
34
|
+
defaults << type
|
35
|
+
end
|
32
36
|
|
33
37
|
[root, defaults]
|
34
38
|
end
|
@@ -47,8 +51,14 @@ module TT
|
|
47
51
|
defaults << :"#{ prefix }.#{ type }.#{ pure_model }"
|
48
52
|
defaults << :"#{ type }.#{ pure_model }"
|
49
53
|
end
|
50
|
-
|
51
|
-
|
54
|
+
|
55
|
+
if base_suffix
|
56
|
+
defaults << :"#{ prefix }.#{ type }.#{ base_suffix }"
|
57
|
+
defaults << :"#{ type }.#{ base_suffix }"
|
58
|
+
else
|
59
|
+
defaults << :"#{ prefix }.#{ type }"
|
60
|
+
defaults << type
|
61
|
+
end
|
52
62
|
|
53
63
|
[root, defaults]
|
54
64
|
end
|
@@ -85,22 +95,25 @@ module TT
|
|
85
95
|
lookup_key_method :c, :common
|
86
96
|
|
87
97
|
def initialize(ns, section = nil)
|
88
|
-
@lookup
|
89
|
-
@
|
98
|
+
@lookup = Utils.lookup(self.class.settings[:prefix], nil)
|
99
|
+
@b_lookup = Utils.lookup(self.class.settings[:prefix], :base)
|
100
|
+
@e_lookup = Utils.lookup(self.class.settings[:prefix], :messages)
|
90
101
|
|
91
102
|
ns = Utils.to_parts(ns).join('.')
|
92
103
|
@config = { ns: ns, root: (section ? "#{ ns }.#{ section }" : ns) }
|
93
104
|
default_model = ns.to_s.singularize
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
@config[:
|
105
|
+
|
106
|
+
@config[:attributes] = @lookup.call(default_model, :attributes)
|
107
|
+
@config[:models] = @lookup.call(default_model, :models)
|
108
|
+
@config[:actions] = @b_lookup.call(default_model, :actions)
|
109
|
+
@config[:enums] = @b_lookup.call(default_model, :enums)
|
110
|
+
@config[:errors] = @e_lookup.call(default_model, :errors)
|
98
111
|
|
99
112
|
@downcase = self.class.settings.fetch(:downcase, Utils::DOWNCASE)
|
100
113
|
end
|
101
114
|
|
102
115
|
def a(name, model_name = nil, custom = {})
|
103
|
-
path, defaults = _resolve(model_name, :actions, name)
|
116
|
+
path, defaults = _resolve(@b_lookup, model_name, :actions, name)
|
104
117
|
|
105
118
|
resource = r(model_name)
|
106
119
|
resources = rs(model_name)
|
@@ -111,12 +124,12 @@ module TT
|
|
111
124
|
end
|
112
125
|
|
113
126
|
def attr(name, model_name = nil)
|
114
|
-
path, defaults = _resolve(model_name, :attributes, name)
|
127
|
+
path, defaults = _resolve(@lookup, model_name, :attributes, name)
|
115
128
|
I18n.t path, default: defaults
|
116
129
|
end
|
117
130
|
|
118
131
|
def enum(name, kind, model_name = nil)
|
119
|
-
path, defaults = _resolve(model_name, :enums, "#{ name }.#{ kind }")
|
132
|
+
path, defaults = _resolve(@b_lookup, model_name, :enums, "#{ name }.#{ kind }")
|
120
133
|
I18n.t path, default: defaults
|
121
134
|
end
|
122
135
|
|
@@ -132,8 +145,9 @@ module TT
|
|
132
145
|
end
|
133
146
|
|
134
147
|
def rs(model_name = nil, count = 10)
|
135
|
-
path, defaults = _resolve(model_name, :models)
|
136
|
-
|
148
|
+
path, defaults = _resolve(@lookup, model_name, :models, nil)
|
149
|
+
# cut from defaults :"#{ orm }.models", :models
|
150
|
+
I18n.t path, default: defaults[0...-2], count: count
|
137
151
|
end
|
138
152
|
|
139
153
|
def t(key, custom = {})
|
@@ -147,21 +161,17 @@ module TT
|
|
147
161
|
@config
|
148
162
|
end
|
149
163
|
|
150
|
-
def _resolve(model_name, type, key = nil)
|
151
|
-
_resolve_with_lookup(@lookup, model_name, type, key)
|
152
|
-
end
|
153
|
-
|
154
164
|
def _resolve_errors(model_name, attr_name, error_name)
|
155
165
|
if attr_name == :base
|
156
|
-
|
166
|
+
_resolve(@e_lookup, model_name, :errors, error_name)
|
157
167
|
else
|
158
|
-
path, _defaults = _resolve(model_name, :errors, "#{ attr_name }.#{ error_name }")
|
168
|
+
path, _defaults = _resolve(@lookup, model_name, :errors, "#{ attr_name }.#{ error_name }")
|
159
169
|
defaults = _defaults + ["errors.messages.#{ error_name }".to_sym]
|
160
170
|
return path, defaults
|
161
171
|
end
|
162
172
|
end
|
163
173
|
|
164
|
-
def
|
174
|
+
def _resolve(lookup, model_name, type, key)
|
165
175
|
paths = model_name ? lookup.call(model_name, type) : _config.fetch(type)
|
166
176
|
if key
|
167
177
|
return "#{ paths.first }.#{ key }", paths.last.map { |i| :"#{ i }.#{ key }" }
|
data/readme.md
CHANGED
@@ -17,8 +17,8 @@ is to look at [Cheatsheet](./cheatsheet.md). The below is shown a brief overview
|
|
17
17
|
```Haml
|
18
18
|
# en:
|
19
19
|
# actions:
|
20
|
-
#
|
21
|
-
#
|
20
|
+
# base:
|
21
|
+
# add: "Add a new %{r}"
|
22
22
|
# attributes:
|
23
23
|
# user:
|
24
24
|
# name: "Name"
|
@@ -94,3 +94,12 @@ Just add `gem "t_t"` into your Gemfile and run `bundle`.
|
|
94
94
|
## Requirements
|
95
95
|
|
96
96
|
Dos-T is tested against Ruby 1.9.3+. If your application uses Ruby on Rails the framework version should be 3.2+
|
97
|
+
|
98
|
+
## Changelog
|
99
|
+
|
100
|
+
- 1.1.0:
|
101
|
+
- Added [the action factory](./docs/action_factory.md)
|
102
|
+
- Improve #attr, #r, #rs methods to make them more compatible with ActiveModel methods
|
103
|
+
- Fix a documentation mismatching
|
104
|
+
- 1.0.1
|
105
|
+
- fix the activerecord integration
|
data/t_t.gemspec
CHANGED
@@ -0,0 +1,60 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
describe "Action factory" do
|
4
|
+
describe "adding an actions" do
|
5
|
+
it 'adds an action' do
|
6
|
+
result = factory(:es) { |f| f.add :sing, es: 'cantar' }
|
7
|
+
assert_equal result, { es: { actions: { base: { sing: 'cantar' } } } }
|
8
|
+
end
|
9
|
+
|
10
|
+
it 'checks a locale action presence' do
|
11
|
+
assert_raises(ArgumentError, 't_t: action `run` is missing for `fr` locale') do
|
12
|
+
factory(:fr) { |f| f.add :run, en: 'Run' }
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
it 'checks a locale rule presence' do
|
17
|
+
assert_raises(ArgumentError, 't_t: `feminine` is an unknown rule for `es` locale') do
|
18
|
+
factory(:es) { |f| f.add :swim, es: f.with_rules('El nada', feminine: 'Ella nada') }
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
it 'checks a valid action type' do
|
23
|
+
assert_raises(ArgumentError, 't_t: the value of `count` action for `fr` locale has a wrong type') do
|
24
|
+
factory(:fr) { |f| f.add :count, fr: 34 }
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
describe "adding an exceptions" do
|
30
|
+
it 'adds an exception' do
|
31
|
+
result = factory(:en) do |f|
|
32
|
+
f.add :add, en: 'Add a new'
|
33
|
+
f.add_exception :user, en: { add: 'Register a new' }
|
34
|
+
end
|
35
|
+
|
36
|
+
assert_equal result, { en: { actions: { base: { add: 'Add a new' }, user: { add: 'Register a new' } } } }
|
37
|
+
end
|
38
|
+
|
39
|
+
it 'checks a locale presence' do
|
40
|
+
assert_raises(ArgumentError, 't_t: `ru` is an unknown locale') do
|
41
|
+
factory(:en) do |f|
|
42
|
+
f.add :add, en: 'Add a new'
|
43
|
+
f.add_exception :user, ru: { add: 'Register a new' }
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
it 'checks an action presence' do
|
49
|
+
assert_raises(ArgumentError, 't_t: `listen` action is not specified. Do it before add an exception') do
|
50
|
+
factory(:en) { |f| f.add_exception :visitor, en: { listen: "listen to a band" } }
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
private
|
56
|
+
|
57
|
+
def factory(*args, &block)
|
58
|
+
TT.define_actions(*args, &block)
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
describe 'Built-in rules' do
|
4
|
+
it '#en__an' do
|
5
|
+
result = with_factory(:en, :an) do |f|
|
6
|
+
f.for(:en) { |l| l.use_rule_for(:an, :alarm) }
|
7
|
+
|
8
|
+
f.add :add, en: f.with_rules('A', an: 'An')
|
9
|
+
end
|
10
|
+
|
11
|
+
assert_equal result[:base][:add], 'A'
|
12
|
+
assert_equal result[:alarm][:add], 'An'
|
13
|
+
end
|
14
|
+
|
15
|
+
it '#de__gender' do
|
16
|
+
list = with_factory(:de, :gender) do |f|
|
17
|
+
f.for(:de) do |l|
|
18
|
+
l.use_rule_for(:feminine, :role)
|
19
|
+
l.use_rule_for(:neuter, :company)
|
20
|
+
end
|
21
|
+
|
22
|
+
f.add :choose_gender, de: f.with_rules('M', feminine: 'F', neuter: 'N')
|
23
|
+
end
|
24
|
+
|
25
|
+
assert_equal list[:base][:choose_gender], 'M'
|
26
|
+
assert_equal list[:role][:choose_gender], 'F'
|
27
|
+
assert_equal list[:company][:choose_gender], 'N'
|
28
|
+
end
|
29
|
+
|
30
|
+
it '#ru__accuse' do
|
31
|
+
list = with_factory(:ru, :accuse) do |f|
|
32
|
+
f.for(:ru) do |l|
|
33
|
+
l.use_rule_for(:accuse, man: { r: 'man', R: 'Man' }, woman: { RS: 'Women', rs: 'women' })
|
34
|
+
end
|
35
|
+
|
36
|
+
f.add :accuse, ru: f.with_rules("%{r} %{rs} %{R} %{RS}", :accuse)
|
37
|
+
end
|
38
|
+
|
39
|
+
assert_equal list[:base][:accuse], "%{r} %{rs} %{R} %{RS}"
|
40
|
+
assert_equal list[:man][:accuse], "man %{rs} Man %{RS}"
|
41
|
+
assert_equal list[:woman][:accuse], "%{r} women %{R} Women"
|
42
|
+
end
|
43
|
+
|
44
|
+
private
|
45
|
+
|
46
|
+
def with_factory(lang, macro)
|
47
|
+
TT.define_actions(lang) do |f|
|
48
|
+
f.activate_rules("#{ lang }__#{ macro }")
|
49
|
+
yield f
|
50
|
+
end[lang][:actions]
|
51
|
+
end
|
52
|
+
end
|
data/tests/lib/model_test.rb
CHANGED
@@ -2,7 +2,7 @@ require 'test_helper'
|
|
2
2
|
|
3
3
|
describe 'Methods related to models' do
|
4
4
|
before do
|
5
|
-
@tt =
|
5
|
+
@tt = TT::Translator.new("admin/users", "spec")
|
6
6
|
end
|
7
7
|
|
8
8
|
describe 'actions' do
|
@@ -44,7 +44,7 @@ describe 'Methods related to models' do
|
|
44
44
|
describe 'attributes' do
|
45
45
|
before do
|
46
46
|
load_i18n(attributes: {
|
47
|
-
|
47
|
+
name: 'Name', phone: 'Phone', email: 'Email',
|
48
48
|
user: { name: 'Nick', phone: 'Notification phone' },
|
49
49
|
admin: { user: { name: 'Contact admin name' } }
|
50
50
|
})
|
@@ -134,7 +134,7 @@ describe 'Methods related to models' do
|
|
134
134
|
|
135
135
|
describe 'resource names' do
|
136
136
|
before do
|
137
|
-
@tt =
|
137
|
+
@tt = TT::Translator.new('public/people')
|
138
138
|
load_i18n({
|
139
139
|
models: { person: { one: "whatever", other: "whatever" }, user: { one: "User", other: "Users" } },
|
140
140
|
activerecord: { models: {
|
data/tests/test_helper.rb
CHANGED
@@ -1,19 +1,18 @@
|
|
1
|
+
# emulate activerecord presence
|
2
|
+
ActiveRecord = nil
|
3
|
+
|
1
4
|
require "minitest/autorun"
|
2
5
|
require "minitest/mock"
|
3
6
|
require "rack/test"
|
4
7
|
require "action_controller"
|
5
8
|
require "t_t"
|
6
9
|
require "t_t/action_factory"
|
7
|
-
require "t_t/action_macros"
|
8
10
|
|
11
|
+
ActiveSupport.run_load_hooks(:active_record, self)
|
9
12
|
ViewTranslator = TT.fork do
|
10
13
|
lookup_key_method :f, :form
|
11
14
|
end
|
12
15
|
|
13
|
-
ARTranslator = TT.fork do
|
14
|
-
settings prefix: :activerecord
|
15
|
-
end
|
16
|
-
|
17
16
|
I18n.backend = I18n::Backend::Simple.new
|
18
17
|
|
19
18
|
class Minitest::Spec
|
@@ -25,3 +24,12 @@ class Minitest::Spec
|
|
25
24
|
I18n.backend.store_translations(:en, data)
|
26
25
|
end
|
27
26
|
end
|
27
|
+
|
28
|
+
class << Minitest::Spec
|
29
|
+
alias :focus :it
|
30
|
+
|
31
|
+
if ENV.has_key?('FOCUS')
|
32
|
+
def it(*args, &block)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: t_t
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.0
|
4
|
+
version: 1.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Sergey Pchelintsev
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2016-02-
|
11
|
+
date: 2016-02-25 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: i18n
|
@@ -120,6 +120,7 @@ files:
|
|
120
120
|
- Gemfile
|
121
121
|
- Rakefile
|
122
122
|
- cheatsheet.md
|
123
|
+
- docs/action_factory.md
|
123
124
|
- examples/simple_app.yml
|
124
125
|
- gemfiles/Gemfile.actionpack-3.1.x
|
125
126
|
- gemfiles/Gemfile.actionpack-3.2.x
|
@@ -127,9 +128,13 @@ files:
|
|
127
128
|
- gemfiles/Gemfile.actionpack-4.1.x
|
128
129
|
- gemfiles/Gemfile.actionpack-4.2.x
|
129
130
|
- lib/t_t.rb
|
131
|
+
- lib/t_t/action_factory.rb
|
132
|
+
- lib/t_t/builtin_rules.rb
|
130
133
|
- readme.md
|
131
134
|
- t_t.gemspec
|
135
|
+
- tests/lib/action_factory_test.rb
|
132
136
|
- tests/lib/action_pack_test.rb
|
137
|
+
- tests/lib/builtin_rules_test.rb
|
133
138
|
- tests/lib/model_test.rb
|
134
139
|
- tests/lib/view_test.rb
|
135
140
|
- tests/test_helper.rb
|