t_t 1.0.1 → 1.1.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/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
|