tolk 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.
- data/MIT-LICENSE +20 -0
- data/README +60 -0
- data/app/controllers/tolk/application_controller.rb +17 -0
- data/app/controllers/tolk/locales_controller.rb +46 -0
- data/app/controllers/tolk/searches_controller.rb +15 -0
- data/app/helpers/tolk/application_helper.rb +33 -0
- data/app/models/tolk/locale.rb +246 -0
- data/app/models/tolk/phrase.rb +22 -0
- data/app/models/tolk/translation.rb +91 -0
- data/app/views/layouts/tolk/application.html.erb +33 -0
- data/app/views/tolk/locales/all.html.erb +64 -0
- data/app/views/tolk/locales/index.html.erb +35 -0
- data/app/views/tolk/locales/show.atom.builder +11 -0
- data/app/views/tolk/locales/show.html.erb +58 -0
- data/app/views/tolk/searches/_form.html.erb +8 -0
- data/app/views/tolk/searches/show.html.erb +55 -0
- data/config/routes.rb +7 -0
- data/init.rb +8 -0
- data/lib/generators/tolk_migration/templates/migrate/create_tolk_tables.rb +38 -0
- data/lib/generators/tolk_migration/tolk_migration_generator.rb +19 -0
- data/lib/tasks/tolk_tasks.rake +43 -0
- data/lib/tolk.rb +6 -0
- data/lib/tolk/engine.rb +7 -0
- data/lib/tolk/import.rb +47 -0
- data/lib/tolk/sync.rb +74 -0
- data/public/tolk/reset.css +30 -0
- data/public/tolk/screen.css +336 -0
- metadata +122 -0
data/lib/tolk.rb
ADDED
data/lib/tolk/engine.rb
ADDED
data/lib/tolk/import.rb
ADDED
@@ -0,0 +1,47 @@
|
|
1
|
+
module Tolk
|
2
|
+
module Import
|
3
|
+
def self.included(base)
|
4
|
+
base.send :extend, ClassMethods
|
5
|
+
end
|
6
|
+
|
7
|
+
module ClassMethods
|
8
|
+
|
9
|
+
def import_secondary_locales
|
10
|
+
locales = Dir.entries(self.locales_config_path)
|
11
|
+
locales = locales.reject {|l| ['.', '..'].include?(l) || !l.ends_with?('.yml') }.map {|x| x.split('.').first } - [Tolk::Locale.primary_locale.name]
|
12
|
+
|
13
|
+
locales.each {|l| import_locale(l) }
|
14
|
+
end
|
15
|
+
|
16
|
+
def import_locale(locale_name)
|
17
|
+
locale = Tolk::Locale.find_or_create_by_name(locale_name)
|
18
|
+
data = locale.read_locale_file
|
19
|
+
|
20
|
+
phrases = Tolk::Phrase.all
|
21
|
+
count = 0
|
22
|
+
|
23
|
+
data.each do |key, value|
|
24
|
+
phrase = phrases.detect {|p| p.key == key}
|
25
|
+
|
26
|
+
if phrase
|
27
|
+
translation = locale.translations.new(:text => value, :phrase => phrase)
|
28
|
+
count = count + 1 if translation.save
|
29
|
+
else
|
30
|
+
puts "[ERROR] Key '#{key}' was found in #{locale_name}.yml but #{Tolk::Locale.primary_language_name} translation is missing"
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
puts "[INFO] Imported #{count} keys from #{locale_name}.yml"
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
38
|
+
|
39
|
+
def read_locale_file
|
40
|
+
locale_file = "#{self.locales_config_path}/#{self.name}.yml"
|
41
|
+
raise "Locale file #{locale_file} does not exists" unless File.exists?(locale_file)
|
42
|
+
|
43
|
+
self.class.flat_hash(YAML::load(IO.read(locale_file))[self.name])
|
44
|
+
end
|
45
|
+
|
46
|
+
end
|
47
|
+
end
|
data/lib/tolk/sync.rb
ADDED
@@ -0,0 +1,74 @@
|
|
1
|
+
module Tolk
|
2
|
+
module Sync
|
3
|
+
def self.included(base)
|
4
|
+
base.send :extend, ClassMethods
|
5
|
+
end
|
6
|
+
|
7
|
+
module ClassMethods
|
8
|
+
def sync!
|
9
|
+
sync_phrases(load_translations)
|
10
|
+
end
|
11
|
+
|
12
|
+
def load_translations
|
13
|
+
I18n.available_locales # force load
|
14
|
+
translations = flat_hash(I18n.backend.send(:translations)[primary_locale.name.to_sym])
|
15
|
+
filter_out_i18n_keys(translations.merge(read_primary_locale_file))
|
16
|
+
end
|
17
|
+
|
18
|
+
def read_primary_locale_file
|
19
|
+
primary_file = "#{self.locales_config_path}/#{self.primary_locale_name}.yml"
|
20
|
+
File.exists?(primary_file) ? flat_hash(YAML::load(IO.read(primary_file))[self.primary_locale_name]) : {}
|
21
|
+
end
|
22
|
+
|
23
|
+
def flat_hash(data, prefix = '', result = {})
|
24
|
+
data.each do |key, value|
|
25
|
+
current_prefix = prefix.present? ? "#{prefix}.#{key}" : key
|
26
|
+
|
27
|
+
if !value.is_a?(Hash) || Tolk::Locale.pluralization_data?(value)
|
28
|
+
result[current_prefix] = value.respond_to?(:stringify_keys) ? value.stringify_keys : value
|
29
|
+
else
|
30
|
+
flat_hash(value, current_prefix, result)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
result.stringify_keys
|
35
|
+
end
|
36
|
+
|
37
|
+
private
|
38
|
+
|
39
|
+
def sync_phrases(translations)
|
40
|
+
primary_locale = self.primary_locale
|
41
|
+
secondary_locales = self.secondary_locales
|
42
|
+
|
43
|
+
# Handle deleted phrases
|
44
|
+
translations.present? ? Tolk::Phrase.destroy_all(["tolk_phrases.key NOT IN (?)", translations.keys]) : Tolk::Phrase.destroy_all
|
45
|
+
|
46
|
+
phrases = Tolk::Phrase.all
|
47
|
+
|
48
|
+
translations.each do |key, value|
|
49
|
+
# Create phrase and primary translation if missing
|
50
|
+
existing_phrase = phrases.detect {|p| p.key == key} || Tolk::Phrase.create!(:key => key)
|
51
|
+
translation = existing_phrase.translations.primary || primary_locale.translations.build(:phrase_id => existing_phrase.id)
|
52
|
+
translation.text = value
|
53
|
+
|
54
|
+
if translation.changed? && !translation.new_record?
|
55
|
+
# Set the primary updated flag if the primary translation has changed and it is not a new record.
|
56
|
+
secondary_locales.each do |locale|
|
57
|
+
if existing_translation = existing_phrase.translations.detect {|t| t.locale_id == locale.id }
|
58
|
+
existing_translation.force_set_primary_update = true
|
59
|
+
existing_translation.save!
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
translation.primary = true
|
65
|
+
translation.save!
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def filter_out_i18n_keys(flat_hash)
|
70
|
+
flat_hash.reject { |key, value| key.starts_with? "i18n" }
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
/*-------------------------------------------------
|
2
|
+
RESET
|
3
|
+
-------------------------------------------------*/
|
4
|
+
|
5
|
+
body,div,dl,dt,dd,ul,ol,li,h1,h2,h3,h4,h5,h6,pre,form,fieldset,input,textarea,p,blockquote,th,td {
|
6
|
+
margin:0;
|
7
|
+
padding:0;
|
8
|
+
}
|
9
|
+
table {
|
10
|
+
border-collapse:collapse;
|
11
|
+
border-spacing:0;
|
12
|
+
}
|
13
|
+
fieldset,img {border:0;}
|
14
|
+
address,caption,cite,code,dfn,th,var {
|
15
|
+
font-style:normal;
|
16
|
+
font-weight:normal;
|
17
|
+
}
|
18
|
+
|
19
|
+
h1,h2,h3,h4,h5,h6 {
|
20
|
+
font-size:100%;
|
21
|
+
font-weight:normal;
|
22
|
+
}
|
23
|
+
|
24
|
+
ol,ul {list-style:none;}
|
25
|
+
caption,th {text-align:left;}
|
26
|
+
q:before,q:after {content:'';}
|
27
|
+
abbr,acronym {border:0;}
|
28
|
+
|
29
|
+
img {border: none;}
|
30
|
+
em em {font-style: normal;}
|
@@ -0,0 +1,336 @@
|
|
1
|
+
/*-------------------------------------------------
|
2
|
+
Defaults
|
3
|
+
-------------------------------------------------*/
|
4
|
+
|
5
|
+
acronym, abbr {
|
6
|
+
font-variant: small-caps;
|
7
|
+
text-transform: lowercase;
|
8
|
+
font-weight: bold;
|
9
|
+
}
|
10
|
+
|
11
|
+
.center {text-align: center;}
|
12
|
+
.large {font-size: larger;}
|
13
|
+
.small {font-size: smaller;}
|
14
|
+
strong {font-weight: bold;}
|
15
|
+
em {font-style: italic;}
|
16
|
+
|
17
|
+
.clear {clear: both;}
|
18
|
+
.clearfix:after { content: "."; display: block; height: 0; clear: both; visibility: hidden; }
|
19
|
+
.clearfix { display: inline-block; }
|
20
|
+
.clearfix{ display: block; }
|
21
|
+
|
22
|
+
a {color: #888;}
|
23
|
+
a:hover {color: #000;}
|
24
|
+
|
25
|
+
/*-------------------------------------------------
|
26
|
+
Layout
|
27
|
+
-------------------------------------------------*/
|
28
|
+
|
29
|
+
body {
|
30
|
+
font-family: "Lucida Sans", "Lucida Grande", "Lucida Sans Unicode", sans-serif;
|
31
|
+
background: #e5e5e5;
|
32
|
+
color: #333;
|
33
|
+
margin: 0;
|
34
|
+
padding: 0;
|
35
|
+
font-size: 14px;
|
36
|
+
line-height: 21px;
|
37
|
+
text-align: left;
|
38
|
+
}
|
39
|
+
|
40
|
+
div#container {
|
41
|
+
margin: 2% 4%;
|
42
|
+
padding: 25px;
|
43
|
+
background: #fff;
|
44
|
+
-webkit-border-radius: 20px;
|
45
|
+
-moz-border-radius: 20px;
|
46
|
+
border-radius: 20px;
|
47
|
+
box-shadow: 0px 0px 15px rgba(00,00,00,0.5);
|
48
|
+
-moz-box-shadow: 0px 0px 15px rgba(00,00,00,0.5);
|
49
|
+
-webkit-box-shadow: 0px 0px 15px rgba(00,00,00,0.5);
|
50
|
+
}
|
51
|
+
|
52
|
+
div#head {
|
53
|
+
margin: -25px -25px 0;
|
54
|
+
background: #111;
|
55
|
+
color: #999;
|
56
|
+
padding: 25px 25px 20px 15px;
|
57
|
+
-webkit-border-top-left-radius: 20px;
|
58
|
+
-webkit-border-top-right-radius: 20px;
|
59
|
+
-moz-border-radius-topleft: 20px;
|
60
|
+
-moz-border-radius-topright: 20px;
|
61
|
+
border-top-left-radius: 20px;
|
62
|
+
border-top-right-radius: 20px;
|
63
|
+
font-size: 18px;
|
64
|
+
}
|
65
|
+
|
66
|
+
div#head a {
|
67
|
+
color: #2fadcf;
|
68
|
+
font-weight: bold;
|
69
|
+
text-decoration: none;
|
70
|
+
}
|
71
|
+
|
72
|
+
div#head span.home {
|
73
|
+
-webkit-border-top-left-radius: 10px;
|
74
|
+
-webkit-border-bottom-left-radius: 10px;
|
75
|
+
-moz-border-radius-topleft: 10px;
|
76
|
+
-moz-border-radius-bottomleft: 10px;
|
77
|
+
border-top-left-radius: 10px;
|
78
|
+
border-bottom-left-radius: 10px;
|
79
|
+
background: #333;
|
80
|
+
padding: 8px 6px 8px 12px;
|
81
|
+
margin-right: 1px;
|
82
|
+
}
|
83
|
+
|
84
|
+
div#head span.locale {
|
85
|
+
color: #fff;
|
86
|
+
font-weight: bold;
|
87
|
+
background: #444;
|
88
|
+
padding: 8px 12px 8px 6px;
|
89
|
+
-webkit-border-top-right-radius: 10px;
|
90
|
+
-webkit-border-bottom-right-radius: 10px;
|
91
|
+
-moz-border-radius-topright: 10px;
|
92
|
+
-moz-border-radius-bottomright: 10px;
|
93
|
+
border-top-right-radius: 10px;
|
94
|
+
border-bottom-right-radius: 10px;
|
95
|
+
}
|
96
|
+
|
97
|
+
div#head span.locale.empty {
|
98
|
+
background: #333;
|
99
|
+
padding-left: 0;
|
100
|
+
margin-left: -6px;
|
101
|
+
}
|
102
|
+
|
103
|
+
div#head span.locale em {
|
104
|
+
font-style: normal;
|
105
|
+
font-weight: normal;
|
106
|
+
}
|
107
|
+
|
108
|
+
div#head span.note {
|
109
|
+
color: #777;
|
110
|
+
font-size: 13px;
|
111
|
+
font-weight: normal;
|
112
|
+
margin-left: 10px;
|
113
|
+
}
|
114
|
+
|
115
|
+
h2,
|
116
|
+
h3 {
|
117
|
+
font-size: 18px;
|
118
|
+
color: #2fadcf;
|
119
|
+
margin: 25px 0 10px;
|
120
|
+
}
|
121
|
+
|
122
|
+
h2 span,
|
123
|
+
h3 span {
|
124
|
+
font-size: 13px;
|
125
|
+
color: #888;
|
126
|
+
}
|
127
|
+
|
128
|
+
ul.locales {
|
129
|
+
margin: 25px 0;
|
130
|
+
}
|
131
|
+
|
132
|
+
ul.locales li {
|
133
|
+
/*width: 230px;*/
|
134
|
+
width: 150px;
|
135
|
+
display: block;
|
136
|
+
float: left;
|
137
|
+
margin: 0 10px 10px 0;
|
138
|
+
/* background: #fff;
|
139
|
+
-webkit-border-radius: 5px;
|
140
|
+
-moz-border-radius: 5px;
|
141
|
+
border-radius: 5px;
|
142
|
+
border: 1px solid #e5e5e5;
|
143
|
+
padding: 5px 10px;*/
|
144
|
+
}
|
145
|
+
|
146
|
+
ul.locales a {
|
147
|
+
font-weight: bold;
|
148
|
+
text-decoration: underline;
|
149
|
+
color: #2fadcf;
|
150
|
+
}
|
151
|
+
|
152
|
+
/*ul.locales li:hover {
|
153
|
+
background: #f5f5f5;
|
154
|
+
border-color: #ccc;
|
155
|
+
}*/
|
156
|
+
|
157
|
+
ul.locales span.missing_translations {
|
158
|
+
color: #fff;
|
159
|
+
font-weight: bold;
|
160
|
+
background: #c00;
|
161
|
+
font-size: 9px;
|
162
|
+
padding: 3px;
|
163
|
+
-webkit-border-radius: 3px;
|
164
|
+
-moz-border-radius: 3px;
|
165
|
+
border-radius: 3px;
|
166
|
+
line-height: 9px;
|
167
|
+
vertical-align: top;
|
168
|
+
display: inline-block;
|
169
|
+
}
|
170
|
+
|
171
|
+
ul.locales span {
|
172
|
+
font-size: 13px;
|
173
|
+
color: #666;
|
174
|
+
}
|
175
|
+
|
176
|
+
div.submit {
|
177
|
+
background: #f5f5f5;
|
178
|
+
margin: 25px -25px -25px;
|
179
|
+
padding: 15px 25px;
|
180
|
+
border-top: 1px dashed #ccc;
|
181
|
+
-webkit-border-bottom-right-radius: 20px;
|
182
|
+
-webkit-border-bottom-left-radius: 20px;
|
183
|
+
-moz-border-radius-bottomright: 20px;
|
184
|
+
-moz-border-radius-bottomleft: 20px;
|
185
|
+
border-bottom-right-radius: 20px;
|
186
|
+
border-bottom-left-radius: 20px;
|
187
|
+
}
|
188
|
+
|
189
|
+
span.updated {
|
190
|
+
font-size: 11px;
|
191
|
+
padding: 1px;
|
192
|
+
background: #ffc;
|
193
|
+
color: #777;
|
194
|
+
margin-bottom: 10px;
|
195
|
+
float: right;
|
196
|
+
}
|
197
|
+
|
198
|
+
div.table_submit {
|
199
|
+
background: #f5f5f5;
|
200
|
+
margin: -25px 0 0;
|
201
|
+
padding: 12px 15px 15px;
|
202
|
+
text-align: left;
|
203
|
+
}
|
204
|
+
|
205
|
+
div.translations {
|
206
|
+
width: 96%;
|
207
|
+
text-align: center;
|
208
|
+
}
|
209
|
+
|
210
|
+
span.notice {
|
211
|
+
background: #ffc;
|
212
|
+
color: #666;
|
213
|
+
font-size: 12px;
|
214
|
+
padding: 2px 5px;
|
215
|
+
margin: -5px 0 15px;
|
216
|
+
display: inline-block;
|
217
|
+
}
|
218
|
+
|
219
|
+
div.original {
|
220
|
+
color: #999;
|
221
|
+
margin: 5px 0;
|
222
|
+
padding: 1px 8px 4px;
|
223
|
+
}
|
224
|
+
|
225
|
+
div.updated {
|
226
|
+
background: #ffc;
|
227
|
+
padding: 1px 8px 4px;
|
228
|
+
}
|
229
|
+
|
230
|
+
table.translations div.original span.key {
|
231
|
+
margin: 0 0 -2px;
|
232
|
+
padding: 0;
|
233
|
+
}
|
234
|
+
|
235
|
+
table.translations div.updated span.key {
|
236
|
+
margin: 0 0 -2px;
|
237
|
+
color: orange !important;
|
238
|
+
padding: 0;
|
239
|
+
}
|
240
|
+
|
241
|
+
/*-------------------------------------------------
|
242
|
+
Translation tables
|
243
|
+
-------------------------------------------------*/
|
244
|
+
|
245
|
+
table.translations {
|
246
|
+
margin: 0 0 25px;
|
247
|
+
width: 100%;
|
248
|
+
text-align: left;
|
249
|
+
}
|
250
|
+
|
251
|
+
table.translations td,
|
252
|
+
table.translations th {
|
253
|
+
font-size: 14px;
|
254
|
+
color: #222;
|
255
|
+
padding: 12px 8px;
|
256
|
+
border-bottom: 1px solid #e5e5e5;
|
257
|
+
vertical-align: top;
|
258
|
+
width: 50%;
|
259
|
+
}
|
260
|
+
|
261
|
+
table.translations th {
|
262
|
+
border-bottom-color: #bbb;
|
263
|
+
font-size: 11px;
|
264
|
+
font-weight: bold;
|
265
|
+
text-transform: uppercase;
|
266
|
+
color: #999;
|
267
|
+
padding-bottom: 2px;
|
268
|
+
}
|
269
|
+
|
270
|
+
table.translations textarea.locale {
|
271
|
+
font-family: "Lucida Sans", "Lucida Grande", "Lucida Sans Unicode", sans-serif;
|
272
|
+
font-size: 14px;
|
273
|
+
line-height: 21px;
|
274
|
+
color: #222;
|
275
|
+
padding: 1px;
|
276
|
+
width: 100%;
|
277
|
+
height: 42px;
|
278
|
+
}
|
279
|
+
|
280
|
+
table.translations span.key {
|
281
|
+
color: #aaa;
|
282
|
+
font-size: 9px;
|
283
|
+
display: block;
|
284
|
+
}
|
285
|
+
|
286
|
+
table.translations td.translation {
|
287
|
+
padding: 10px 8px;
|
288
|
+
}
|
289
|
+
|
290
|
+
table.translations tr.active td {
|
291
|
+
background: #edf9fe;
|
292
|
+
}
|
293
|
+
|
294
|
+
table.translations .highlight {
|
295
|
+
background-color: yellow;
|
296
|
+
}
|
297
|
+
|
298
|
+
/*-------------------------------------------------
|
299
|
+
Pagination
|
300
|
+
-------------------------------------------------*/
|
301
|
+
|
302
|
+
div.paginate {
|
303
|
+
margin: 15px auto 20px;
|
304
|
+
font-size: 12px;
|
305
|
+
color: #777;
|
306
|
+
}
|
307
|
+
|
308
|
+
div.paginate a,
|
309
|
+
div.paginate span {
|
310
|
+
padding: 2px 6px;
|
311
|
+
text-decoration: none;
|
312
|
+
}
|
313
|
+
|
314
|
+
div.paginate a:hover,
|
315
|
+
div.paginate span.current {
|
316
|
+
-webkit-border-radius: 5px;
|
317
|
+
-moz-border-radius: 5px;
|
318
|
+
border-radius: 5px;
|
319
|
+
background: #eee;
|
320
|
+
color: #333;
|
321
|
+
}
|
322
|
+
|
323
|
+
div.paginate .next_page,
|
324
|
+
div.paginate .prev_page {
|
325
|
+
margin: 0 15px;
|
326
|
+
-webkit-border-radius: 5px;
|
327
|
+
-moz-border-radius: 5px;
|
328
|
+
border-radius: 5px;
|
329
|
+
border: 1px solid #bbb;
|
330
|
+
padding: 4px 8px;
|
331
|
+
}
|
332
|
+
|
333
|
+
div.paginate .disabled {
|
334
|
+
color: #ccc;
|
335
|
+
border-color: #eee;
|
336
|
+
}
|