tr8n 3.1.6 → 3.1.7
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile.lock +1 -1
- data/{local/tr8n_server/app/assets/stylesheets/admin.css → app/assets/stylesheets/tr8n/admin.css.scss} +0 -0
- data/app/assets/stylesheets/tr8n/components.css.scss +211 -0
- data/app/assets/stylesheets/tr8n/layout.css.scss +143 -0
- data/app/models/tr8n/language_rule.rb +31 -29
- data/app/models/tr8n/sync_log.rb +114 -19
- data/app/models/tr8n/translation.rb +43 -40
- data/app/models/tr8n/translation_key.rb +48 -18
- data/config/routes.rb +1 -1
- data/lib/generators/tr8n/templates/config/tr8n/config.yml +2 -2
- data/lib/generators/tr8n/templates/layouts/tr8n.html.erb +4 -1
- data/lib/generators/tr8n/templates/layouts/tr8n_admin.html.erb +5 -3
- data/lib/generators/tr8n/tr8n_generator.rb +1 -1
- data/lib/tasks/tr8n.rake +4 -2
- data/lib/tr8n/config.rb +1 -1
- data/lib/tr8n/version.rb +1 -1
- data/local/tr8n_server/app/assets/stylesheets/admin.css.scss +200 -0
- data/local/tr8n_server/app/assets/stylesheets/{application.css → application.css.scss} +0 -0
- metadata +43 -42
- data/app/controllers/tr8n/api/v1/sync_controller.rb +0 -30
- data/app/controllers/tr8n/api/v1/translator_controller.rb +0 -34
data/Gemfile.lock
CHANGED
File without changes
|
@@ -0,0 +1,211 @@
|
|
1
|
+
/* General */
|
2
|
+
|
3
|
+
.flt_r {float:right}
|
4
|
+
.quiet {color:#888 !important;}
|
5
|
+
.strong {font-weight:bold}
|
6
|
+
.em {font-style:italic}
|
7
|
+
.small {font-size:11px}
|
8
|
+
.xsmall {font-size:10px}
|
9
|
+
.sub_heading {border-bottom:solid 1px #ddd;margin-bottom:3px;}
|
10
|
+
.notice_msg {color:darkorange;background:#feffef;padding:3px 5px;margin:0 0 3px 0}
|
11
|
+
.warning_msg {color:darkred;background:pink;padding:3px 5px;margin:0 0 3px 0}
|
12
|
+
.focus {background-color:#f1f5ff}
|
13
|
+
.highlight {background-color:#fefff1}
|
14
|
+
.highlight_hover:hover {background:#fefff1}
|
15
|
+
.nowrap {white-space:nowrap}
|
16
|
+
.underscore {border-bottom:solid 1px #eee}
|
17
|
+
|
18
|
+
.light_bevel_hr {height:0px;border:solid 1px #e1e5e8;border-bottom-color:#fff;}
|
19
|
+
|
20
|
+
.padding_0 {padding:0px !important;}
|
21
|
+
.padding_5 {padding:5px !important;}
|
22
|
+
.padding_10 {padding:10px !important;}
|
23
|
+
.padding_15 {padding:15px !important;}
|
24
|
+
|
25
|
+
/* Module Skins */
|
26
|
+
|
27
|
+
.basic .inner {padding:10px;}
|
28
|
+
|
29
|
+
.outset .inner {border:1px solid #D7D7D7;border-color:#e9e9e9 #cdcdcd #a4a4a4;border-width:1px 1px 2px;padding:10px;}
|
30
|
+
.outset b {background-image:url('rounded_corners_sprite.gif');}
|
31
|
+
.outset .tl {background-position: 0 -70px;}
|
32
|
+
.outset .tr {background-position: -10px -70px;}
|
33
|
+
.outset .bl {background-position: 0 -80px;}
|
34
|
+
.outset .br {background-position: -10px -80px;}
|
35
|
+
|
36
|
+
.inset .inner {border:1px solid #e1e5e8;border-color:#b1b3b7 #e1e5e8 #e1e5e8;border-width:2px 1px 1px;padding:10px;}
|
37
|
+
.inset b {background-image:url('rounded_corners_sprite.gif');}
|
38
|
+
.inset .tl {background-position: 0 -93px;}
|
39
|
+
.inset .tr {background-position: -10px -93px;}
|
40
|
+
.inset .bl {background-position: 0 -103px;}
|
41
|
+
.inset .br {background-position: -10px -103px;}
|
42
|
+
|
43
|
+
.rounded .inner {padding:10px;text-align:left;}
|
44
|
+
.rounded b {background-image:url('rounded_corners_sprite.gif');}
|
45
|
+
.rounded .tl {background-position: 0 -116px;}
|
46
|
+
.rounded .tr {background-position: -10px -116px;}
|
47
|
+
.rounded .bl {background-position: 0 -126px;}
|
48
|
+
.rounded .br {background-position: -10px -126px;}
|
49
|
+
|
50
|
+
|
51
|
+
/* Module Backgrounds */
|
52
|
+
|
53
|
+
.highlight_bevel {background:url('/images/highlight_bkgd.gif') repeat-x 0 bottom #fefff1}
|
54
|
+
.focus_bevel {background:url('/images/focus_bkgd.gif') repeat-x 0 bottom #edf2f8}
|
55
|
+
.success_bevel {background:url('/images/success_bkgd.gif') repeat-x 0 bottom #eefce7}
|
56
|
+
|
57
|
+
|
58
|
+
/* Module Headers */
|
59
|
+
.pagination_hd {border-bottom:solid 1px #ddd;padding:5px 0}
|
60
|
+
|
61
|
+
/* Module Body */
|
62
|
+
|
63
|
+
|
64
|
+
/* Module Footers */
|
65
|
+
.action_ft {background:#fbfbfb;border-top:solid 1px #eee;padding:15px 20px;}
|
66
|
+
.pagination_ft {border-top:solid 1px #ddd;padding:5px 0;margin-top:-1px;}
|
67
|
+
|
68
|
+
|
69
|
+
/* Forms */
|
70
|
+
|
71
|
+
.field {clear:both;padding:5px 0 7px 0;overflow:hidden;}
|
72
|
+
.field_hd {float:left;width:145px;text-align:right;color:#666;}
|
73
|
+
.field_bd {margin-left:165px;clear:right}
|
74
|
+
.field_section {float:left;margin-right:5px;color:#999;font-size:10px;}
|
75
|
+
.field_note {margin-top:5px;font-size:10px;color:#666;clear:both;}
|
76
|
+
.field_hint {float:right;font-size:10px;color:#666;width:170px;line-height:11px;font-weight:normal}
|
77
|
+
|
78
|
+
.xshort_input {width:50px}
|
79
|
+
.short_input {width:100px}
|
80
|
+
.medium_input {width:150px}
|
81
|
+
.long_input {width:200px}
|
82
|
+
.xlong_input {width:250px}
|
83
|
+
.full_width_input {width:98%}
|
84
|
+
|
85
|
+
|
86
|
+
/* Lists */
|
87
|
+
|
88
|
+
ol.simple_list li {list-style-type: decimal; margin-left:40px;}
|
89
|
+
ul.simple_list li {list-style-type:disc; margin-left:0px;padding:4px 0}
|
90
|
+
|
91
|
+
.nav_list,
|
92
|
+
.horiz_list,
|
93
|
+
.right_horiz_list {overflow:hidden;text-align:left}
|
94
|
+
|
95
|
+
.nav_list li {margin-top:-1px;border-top:solid 1px #ced2e4;padding:3px 5px;zoom:1}
|
96
|
+
.nav_list li:hover {background-color:#ebeef1}
|
97
|
+
.nav_list li a {text-decoration:none;}
|
98
|
+
|
99
|
+
.horiz_list li {margin-left:-11px;border-left:solid 1px #ccc;padding:0px 10px;margin-right:10px;float:left;}
|
100
|
+
.right_horiz_list li {margin-right:-11px;border-right:solid 1px #ccc;padding:0px 10px;margin-left:10px;float:left;}
|
101
|
+
|
102
|
+
.checklist li {overflow:hidden;padding:0px 0}
|
103
|
+
.checklist input {float:left;}
|
104
|
+
.checklist label {margin:0 0 5px 25px;display:block}
|
105
|
+
|
106
|
+
.segmented_list {overflow:hidden;}
|
107
|
+
.segmented_list li {padding:7px 0;border-bottom:solid 1px #eee;margin-bottom:-1px}
|
108
|
+
|
109
|
+
.block_list li {padding:3px 0}
|
110
|
+
|
111
|
+
|
112
|
+
/* Pagination */
|
113
|
+
|
114
|
+
.pagination {overflow:hidden;zoom:1}
|
115
|
+
.pagination li {padding:0 3px;margin-right:2px;float:left;font-weight:bold;}
|
116
|
+
|
117
|
+
|
118
|
+
|
119
|
+
/* Buttons */
|
120
|
+
|
121
|
+
.btn {position:relative;border:0;padding:0;margin:0 2px 0 -2px;cursor:pointer;overflow:visible;background:transparent url('/images/site_sprite.gif') no-repeat right -100px;font-size:11px;font-weight:bold;text-align:center;font-family:Arial}
|
122
|
+
.btn span {position:relative;display:block;white-space:nowrap;background:transparent url('/images/site_sprite.gif') no-repeat left top;}
|
123
|
+
button::-moz-focus-inner {border:none;}
|
124
|
+
@media screen and (-webkit-min-device-pixel-ratio:0) {
|
125
|
+
.btn span {margin-top:-1px;}
|
126
|
+
}
|
127
|
+
a.btn {display:block;display:-moz-inline-box;display:inline-block;vertical-align:middle}
|
128
|
+
a.btn span {display:block;margin-top:0px}
|
129
|
+
|
130
|
+
/* Button Skins */
|
131
|
+
|
132
|
+
.button {-webkit-border-radius:2px;-moz-border-radius:2px;border-radius:2px;-webkit-box-shadow: rgba(0, 0, 0, 0.246094) 0px 1px 3px;-moz-box-shadow: rgba(0, 0, 0, 0.246094) 0px 1px 3px;background-color: #222;border-bottom: 1px solid rgba(0, 0, 0, 0.246094);color: white !important;cursor: pointer;display: inline-block;font-size: 13px;font-weight: bold;line-height: 1;overflow: visible;padding: 5px 15px 6px;position: relative;text-decoration: none !important;width: auto;}
|
133
|
+
|
134
|
+
.super.button {background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAAeCAYAAADtlXTHAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAABYSURBVHjaZM27EcAgDANQSzZZgP0XpKLh20BM7mjS6J7kwrrWEuScgZQS2HsXjjHEdaqytfZ4kLXWU81lruAK9wqWUuBVvni3n+4Pc9nRnFOUZFQAcQswAI/sUZSGPd0tAAAAAElFTkSuQmCC) repeat-x 0 bottom;background-position:0 bottom;border: 1px solid rgba(0, 0, 0, 0.246094);font-size: 12px;padding: 0px;}
|
135
|
+
.super.button span {border-top: 1px solid rgba(255, 255, 255, 0.199219);display: block;line-height: 1;padding: 4px 15px 6px;}
|
136
|
+
|
137
|
+
.green.button {background-color: #91BD09;}
|
138
|
+
.green.button:hover {background-color:#749a02}
|
139
|
+
|
140
|
+
.blue.button {background-color:#5689c0}
|
141
|
+
.blue.button:hover {background-color:#4a77a8}
|
142
|
+
|
143
|
+
.large.super.button span {padding: 7px 20px 9px;font-size:13px;}
|
144
|
+
.xlarge.super.button span {padding: 8px 20px 10px;font-size:16px;}
|
145
|
+
|
146
|
+
/* Icons */
|
147
|
+
|
148
|
+
.icn {background:url(/images/icon_sprite.gif) no-repeat 0 0;}
|
149
|
+
|
150
|
+
|
151
|
+
|
152
|
+
|
153
|
+
/* Tabs */
|
154
|
+
|
155
|
+
.tab_nav {list-style-type:none;overflow:auto;margin-bottom:0px !important;position:relative}
|
156
|
+
.tab_nav li {float:left;margin-right:2px;cursor:pointer;}
|
157
|
+
.tab_nav li a {display:block;padding:6px 10px 6px 10px;}
|
158
|
+
|
159
|
+
/* Tables */
|
160
|
+
|
161
|
+
.data_table {padding:0;width:auto;margin:0}
|
162
|
+
.data_table td, .data_table th {vertical-align:top;padding:1px 0px 2px 0px}
|
163
|
+
.data_table th {width:115px;color:#777;font-weight:normal;padding-right:6px}
|
164
|
+
|
165
|
+
.data_table.small th {width:70px}
|
166
|
+
|
167
|
+
.segmented_table {width:100%;}
|
168
|
+
.segmented_table td {border-bottom:solid 1px #eee;padding:10px 5px}
|
169
|
+
.segmented_table .last td {border:none}
|
170
|
+
|
171
|
+
.skinny_cells td {padding:5px}
|
172
|
+
.fat_cells td {padding:10px 5px}
|
173
|
+
|
174
|
+
.txt_c {text-align:center;}
|
175
|
+
.txt_l {text-align:left;}
|
176
|
+
.txt_r {text-align:right;}
|
177
|
+
.txt_t {vertical-align:top;}
|
178
|
+
.txt_b {vertical-align:bottom;}
|
179
|
+
.txt_m {vertical-align:middle;}
|
180
|
+
|
181
|
+
.min_cell_width {width:1%}
|
182
|
+
.max_cell_width {width:99%}
|
183
|
+
|
184
|
+
.mrgn_v {margin:10px 0}
|
185
|
+
.mrgn_h {margin:0 10px}
|
186
|
+
|
187
|
+
/* Media Block */
|
188
|
+
|
189
|
+
.media {overflow:hidden;_overflow:visible; zoom:1;}
|
190
|
+
.media .img {float:left;margin-right: 10px;}
|
191
|
+
.media .img img {display:block;}
|
192
|
+
.media .imgExt {float:right; margin-left: 10px;}
|
193
|
+
|
194
|
+
|
195
|
+
|
196
|
+
|
197
|
+
/* Home */
|
198
|
+
|
199
|
+
.home_banner {background:url('/images/bubbles_bkgd.jpg');-webkit-border-radius:5px 5px 0 0;-moz-border-radius:5px 5px 0 0;border-radius:5px 5px 0 0;padding:50px 25px 0px 25px;height:220px;border-bottom:solid 10px #c2d6eb}
|
200
|
+
.home_banner h1 {font:normal bold 45px Arial, Helvetica, sans-serif;color:#fff;text-shadow:0 -1px 1px #000;letter-spacing:-2px}
|
201
|
+
.home_banner h2 {font:normal normal 24px Arial, Helvetica, sans-serif;color:#ccc;text-shadow:0 -1px 1px #000;width:520px;}
|
202
|
+
|
203
|
+
|
204
|
+
.benefit_details {line-height:20px;height:70px;}
|
205
|
+
.page_form {margin:0 50px}
|
206
|
+
|
207
|
+
.tr8n {
|
208
|
+
width:auto !important;
|
209
|
+
overflow:hidden !important
|
210
|
+
}
|
211
|
+
|
@@ -0,0 +1,143 @@
|
|
1
|
+
@mixin shadow($info) {
|
2
|
+
text-shadow:$info;
|
3
|
+
-moz-text-shadow:$info;
|
4
|
+
-webkit-text-shadow:$info;
|
5
|
+
}
|
6
|
+
|
7
|
+
@mixin rounded-corners($radius) {
|
8
|
+
border-radius: $radius;
|
9
|
+
-moz-border-radius: $radius;
|
10
|
+
-webkit-border-radius: $radius;
|
11
|
+
}
|
12
|
+
|
13
|
+
body {margin:0;padding:0;font:normal normal 12px Arial, Helvetica, sans-serif;text-align:left;background:#f5f5f5;color:#444;}
|
14
|
+
|
15
|
+
h1,h2,h3,h4,h5,h6 {margin:0 0 5px 0;padding:0;line-height: 1em;}
|
16
|
+
h1,.h1 {font:normal normal 35px Georgia, serif;color:#424c56;letter-spacing:-1px;}
|
17
|
+
h2,.h2 {font: normal normal normal 24px/normal Arial, Helvetica, sans-serif;color:#666;margin:0 0 8px 0;letter-spacing:-1px;}
|
18
|
+
h3,.h3 {font: normal normal bold 18px/normal Arial, Helvetica, sans-serif;color:#555;margin:0 0 8px 0;padding-bottom:5px;}
|
19
|
+
h4,.h4 {font:normal normal 14px Arial, Helvetica, sans-serif;color:#444;padding:6px;}
|
20
|
+
h5,.h5 {font:normal bold 11px Arial, Helvetica, sans-serif;color:#999}
|
21
|
+
h6,.h6 {font:normal bold 11px Arial, Helvetica, sans-serif;color:#444}
|
22
|
+
|
23
|
+
|
24
|
+
a {color:#3B5998;text-decoration: none;outline:none}
|
25
|
+
a:focus, a:hover {text-decoration: underline }
|
26
|
+
a:visited {color:#3B5998;}
|
27
|
+
|
28
|
+
p {margin:0 0 10px 0;}
|
29
|
+
table {border-collapse:collapse;border-spacing:0}
|
30
|
+
table,ul,ol,dt,dl,dd {padding:0;margin:0;list-style-type:none}
|
31
|
+
td,th {vertical-align:top}
|
32
|
+
img {vertical-align:middle;border:none}
|
33
|
+
input, select, textarea {vertical-align:middle;border:solid 1px #eee;border-color:#bbb #eee #eee #bbb;border-width:1px;font:normal normal 11px Arial, Helvetica, sans-serif;color:#444;outline:none;padding:3px;}
|
34
|
+
select {padding:2px;}
|
35
|
+
input[type=checkbox],
|
36
|
+
input[type=radio],
|
37
|
+
input[type=file],
|
38
|
+
checkbox,
|
39
|
+
radio {border:none;padding:0;}
|
40
|
+
|
41
|
+
|
42
|
+
/* Page Template */
|
43
|
+
|
44
|
+
.page_fixed {width:1000px;margin:0 auto;}
|
45
|
+
.page_elastic {width:auto;margin:0;}
|
46
|
+
.page_fluid {max-width:1100px;min-width:925px;_width:960px;margin:0 auto;padding:0 25px;}
|
47
|
+
|
48
|
+
.page_body,
|
49
|
+
.page_body .main_col {overflow:hidden;_overflow:visible;_zoom:1;}
|
50
|
+
.page_body .left_col {float:left; width:250px;_margin-right:-3px;}
|
51
|
+
.page_body .right_col {float:right; width: 300px;_margin-left:-3px;}
|
52
|
+
|
53
|
+
|
54
|
+
/* Header */
|
55
|
+
|
56
|
+
.page_head {background:#fff;border-bottom: 1px solid #DDD;border-top: 4px solid #6699cc;margin-bottom: 15px;padding: 5px 15px 2px;}
|
57
|
+
.logo a {
|
58
|
+
font-family:Arial;
|
59
|
+
color:#424c56;
|
60
|
+
text-decoration:none;
|
61
|
+
@include shadow(2px 3px 3px 2px rgba(0,0,0,0.6));
|
62
|
+
}
|
63
|
+
.logo a:hover {color:#000}
|
64
|
+
.top_nav {padding-top:13px;}
|
65
|
+
|
66
|
+
/* Grids */
|
67
|
+
|
68
|
+
.line, .last_unit {overflow:hidden;_overflow:visible;_zoom:1; }
|
69
|
+
.unit {float:left;_zoom:1;}
|
70
|
+
.size_1of1 {float:none;}
|
71
|
+
.size_1of2 {width:50%;}
|
72
|
+
.size_1of3 {width:33.33333%;}
|
73
|
+
.size_2of3 {width:66.66666%;}
|
74
|
+
.size_1of4 {width:25%;}
|
75
|
+
.size_3of4 {width:75%;}
|
76
|
+
.size_1of5 {width:20%;}
|
77
|
+
.size_2of5 {width:40%;}
|
78
|
+
.size_3of5 {width:60%;}
|
79
|
+
.size_4of5 {width:80%;}
|
80
|
+
.last_unit {float:none;_position:relative;_left:-3px;_margin-right:-3px;width:auto;}
|
81
|
+
|
82
|
+
|
83
|
+
/* Page Content */
|
84
|
+
|
85
|
+
.content_module {margin:0 10px 10px;}
|
86
|
+
.content_inner {
|
87
|
+
border:1px solid #D7D7D7;
|
88
|
+
border-color:#e9e9e9 #cdcdcd #a4a4a4;
|
89
|
+
border-width:1px 1px 2px;
|
90
|
+
background:#fff;
|
91
|
+
@include rounded-corners(10px);
|
92
|
+
}
|
93
|
+
|
94
|
+
|
95
|
+
|
96
|
+
.content_hd,
|
97
|
+
.content_bd,
|
98
|
+
.content_ft {overflow:hidden;zoom:1;padding:25px}
|
99
|
+
|
100
|
+
|
101
|
+
|
102
|
+
/* Module */
|
103
|
+
|
104
|
+
.module {margin:10px;}
|
105
|
+
.hd,.bd,.ft {overflow:hidden;_overflow:visible;_zoom:1;}
|
106
|
+
.inner {position:relative;}
|
107
|
+
|
108
|
+
|
109
|
+
/* Rounded Corners */
|
110
|
+
|
111
|
+
.tl, .tr, .bl, .br {height:10px; width:10px;position:absolute;}
|
112
|
+
.tl {background-position: left top;left:0}
|
113
|
+
.tr {background-position: right top;}
|
114
|
+
.bl {background-position: left bottom;left:0}
|
115
|
+
.br {background-position: right bottom;}
|
116
|
+
.br,.tr {right:0}
|
117
|
+
.tr,.tl {overflow:hidden;margin-bottom:-32000px;}
|
118
|
+
.bl,.br {bottom:0}
|
119
|
+
|
120
|
+
|
121
|
+
/* Complex Module */
|
122
|
+
|
123
|
+
.complex {overflow:visible;margin: 10px 20px 20px 10px; background-position:left top;}
|
124
|
+
.complex .inner {right:-10px; bottom:-10px; background-position:right bottom;padding:0 10px 10px 0;}
|
125
|
+
.complex .tl, .complex .br {display:none;}
|
126
|
+
.complex .bl {bottom:-10px;}
|
127
|
+
.complex .tr {right:-10px;}
|
128
|
+
|
129
|
+
|
130
|
+
/* Media Block */
|
131
|
+
|
132
|
+
.media {overflow:auto;zoom:1}
|
133
|
+
.media .thumb {width:100px;float:left;}
|
134
|
+
.media .desc {margin-left:110px;}
|
135
|
+
|
136
|
+
|
137
|
+
/* Clear Floats */
|
138
|
+
.clearfix:after {visibility: hidden;display: block;font-size: 0;content: " ";clear: both;height: 0;}
|
139
|
+
.clearfix {display: inline-block; }
|
140
|
+
/* start commented backslash hack \*/
|
141
|
+
* html .clearfix {height: 1%;}
|
142
|
+
.clearfix {display:block;}
|
143
|
+
/* close commented backslash hack */
|
@@ -61,28 +61,6 @@ class Tr8n::LanguageRule < ActiveRecord::Base
|
|
61
61
|
dependency
|
62
62
|
end
|
63
63
|
|
64
|
-
# {"locale"=>"ru", "label"=>"{count} сообщения", "rank"=>1, "rules"=>[
|
65
|
-
# {"token"=>"count", "type"=>"number", "definition"=>
|
66
|
-
# {"multipart"=>true, "part1"=>"ends_in", "value1"=>"2,3,4", "operator"=>"and", "part2"=>"does_not_end_in", "value2"=>"12,13,14"}
|
67
|
-
# }
|
68
|
-
# ]
|
69
|
-
# }
|
70
|
-
|
71
|
-
def self.for_definition(lang, translator, type, definition, opts = {})
|
72
|
-
opts[:force_create] ||= false
|
73
|
-
|
74
|
-
rule_class = Tr8n::Config.language_rule_dependencies[type]
|
75
|
-
return if rule_class == nil # unsupported rule type, skip this completely
|
76
|
-
|
77
|
-
rule_class.for(lang).each do |rule|
|
78
|
-
return rule if rule.definition == definition
|
79
|
-
end
|
80
|
-
|
81
|
-
if opts[:force_create]
|
82
|
-
rule_class.create(:language => lang, :translator => translator, :definition => definition)
|
83
|
-
end
|
84
|
-
end
|
85
|
-
|
86
64
|
# TDOD: switch to using keyword
|
87
65
|
def self.dependency
|
88
66
|
raise Tr8n::Exception.new("This method must be implemented in the extending rule")
|
@@ -102,13 +80,6 @@ class Tr8n::LanguageRule < ActiveRecord::Base
|
|
102
80
|
sanitize_values(values).join(", ")
|
103
81
|
end
|
104
82
|
|
105
|
-
def to_api_hash
|
106
|
-
{
|
107
|
-
:type => self.class.keyword,
|
108
|
-
:definition => definition
|
109
|
-
}
|
110
|
-
end
|
111
|
-
|
112
83
|
def evaluate(token_value)
|
113
84
|
raise Tr8n::Exception.new("This method must be implemented in the extending rule")
|
114
85
|
end
|
@@ -149,4 +120,35 @@ class Tr8n::LanguageRule < ActiveRecord::Base
|
|
149
120
|
Tr8n::Cache.delete("language_rule_#{id}")
|
150
121
|
end
|
151
122
|
|
123
|
+
###############################################################
|
124
|
+
## Synchronization Methods
|
125
|
+
###############################################################
|
126
|
+
def to_sync_hash(token)
|
127
|
+
{
|
128
|
+
"token" => token,
|
129
|
+
"type" => self.class.keyword,
|
130
|
+
"definition" => definition
|
131
|
+
}
|
132
|
+
end
|
133
|
+
|
134
|
+
# {"locale"=>"ru", "label"=>"{count} сообщения", "rank"=>1, "rules"=>[
|
135
|
+
# {"token"=>"count", "type"=>"number", "definition"=>
|
136
|
+
# {"multipart"=>true, "part1"=>"ends_in", "value1"=>"2,3,4", "operator"=>"and", "part2"=>"does_not_end_in", "value2"=>"12,13,14"}
|
137
|
+
# }
|
138
|
+
# ]
|
139
|
+
# }
|
140
|
+
|
141
|
+
def self.create_from_sync_hash(lang, translator, rule_hash, opts = {})
|
142
|
+
return unless rule_hash["token"] and rule_hash["type"] and rule_hash["definition"]
|
143
|
+
|
144
|
+
rule_class = Tr8n::Config.language_rule_dependencies[rule_hash["type"]]
|
145
|
+
return unless rule_class # unsupported rule type, skip this completely
|
146
|
+
|
147
|
+
rule_class.for(lang).each do |rule|
|
148
|
+
return rule if rule.definition == rule_hash["definition"]
|
149
|
+
end
|
150
|
+
|
151
|
+
rule_class.create(:language => lang, :translator => translator, :definition => rule_hash["definition"])
|
152
|
+
end
|
153
|
+
|
152
154
|
end
|
data/app/models/tr8n/sync_log.rb
CHANGED
@@ -23,25 +23,73 @@
|
|
23
23
|
|
24
24
|
class Tr8n::SyncLog < ActiveRecord::Base
|
25
25
|
|
26
|
-
def self.sync
|
27
|
-
|
28
|
-
|
26
|
+
def self.sync(opts = {})
|
27
|
+
sync_log = Tr8n::SyncLog.create(:started_at => Time.now)
|
28
|
+
|
29
29
|
translation_count = 0
|
30
30
|
payload = []
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
31
|
+
batch_count = 0
|
32
|
+
total_key_count = 0
|
33
|
+
|
34
|
+
log("Begin synchronization process...")
|
35
|
+
log("Registering keys...")
|
36
|
+
|
37
|
+
conditions = "synced_at is null or updated_at > synced_at"
|
38
|
+
conditions = nil if opts[:force]
|
39
|
+
|
40
|
+
# STDOUT.sync = true
|
41
|
+
|
42
|
+
Tr8n::TranslationKey.find_each(:conditions => conditions, :batch_size => Tr8n::Config.synchronization_batch_size) do |key|
|
43
|
+
total_key_count += 1
|
44
|
+
sync_hash = key.to_sync_hash
|
45
|
+
payload << sync_hash
|
35
46
|
|
36
|
-
|
47
|
+
key.mark_as_synced!
|
37
48
|
|
38
|
-
if
|
49
|
+
# if sync_hash["label"] == "you have {count||message}"
|
50
|
+
# payload << sync_hash
|
51
|
+
# end
|
52
|
+
|
53
|
+
if payload.size == Tr8n::Config.synchronization_batch_size
|
54
|
+
# pp "Sending #{batch_count+1} batch of #{Tr8n::Config.synchronization_batch_size} keys..."
|
55
|
+
batch_count += 1
|
39
56
|
exchange(payload)
|
57
|
+
payload = []
|
40
58
|
end
|
41
59
|
end
|
42
|
-
|
60
|
+
|
61
|
+
if payload.size > 0
|
62
|
+
# pp "Sending final batch..."
|
63
|
+
batch_count += 1
|
64
|
+
exchange(payload, opts)
|
65
|
+
end
|
66
|
+
|
67
|
+
sync_log.keys_sent = total_key_count
|
68
|
+
|
69
|
+
log("Sent #{total_key_count} keys in #{batch_count} batches.")
|
70
|
+
|
71
|
+
batch_count = 0
|
72
|
+
total_key_count = 0
|
73
|
+
|
74
|
+
unless opts[:force]
|
75
|
+
log("Downloading translations...")
|
76
|
+
|
77
|
+
key_count = download
|
78
|
+
while key_count > 0
|
79
|
+
batch_count += 1
|
80
|
+
total_key_count += key_count
|
81
|
+
key_count = download(opts)
|
82
|
+
end
|
83
|
+
log("Downloaded #{total_key_count} keys in #{batch_count} batches.")
|
84
|
+
end
|
85
|
+
|
86
|
+
sync_log.keys_received = total_key_count
|
87
|
+
sync_log.finished_at = Time.now
|
88
|
+
sync_log.save
|
89
|
+
|
43
90
|
rescue Exception => ex
|
44
91
|
pp ex.message
|
92
|
+
pp ex.backtrace
|
45
93
|
end
|
46
94
|
|
47
95
|
def self.access_token
|
@@ -49,28 +97,75 @@ class Tr8n::SyncLog < ActiveRecord::Base
|
|
49
97
|
uri = URI.parse("#{Tr8n::Config.synchronization_server}/platform/oauth/request_token?client_id=#{Tr8n::Config.synchronization_key}&client_secret=#{Tr8n::Config.synchronization_secret}&grant_type=client_credentials")
|
50
98
|
response = Net::HTTP.get_response(uri)
|
51
99
|
data = JSON.parse(response.body)
|
52
|
-
raise Tr8n::Exception.new("Failed to get access token") unless data["
|
100
|
+
raise Tr8n::Exception.new("Failed to get access token") unless data["access_token"]
|
53
101
|
data["access_token"]
|
54
102
|
end
|
55
103
|
end
|
56
104
|
|
57
|
-
def self.exchange(payload)
|
58
|
-
uri = URI.parse("#{Tr8n::Config.synchronization_server}/api/sync")
|
105
|
+
def self.exchange(payload, opts = {})
|
106
|
+
uri = URI.parse("#{Tr8n::Config.synchronization_server}/api/application/sync")
|
107
|
+
params = {:method => "register", :batch_size => Tr8n::Config.synchronization_batch_size, :translation_keys => payload}
|
59
108
|
|
60
109
|
req = Net::HTTP::Post.new(uri.path)
|
61
|
-
req.body = JSON.generate({:translation_keys => payload})
|
62
110
|
req["Content-Type"] = "application/json"
|
63
111
|
req["Authorization"] = "Bearer #{access_token}"
|
112
|
+
req.body = params.to_json
|
64
113
|
|
65
|
-
|
66
|
-
response = http.start {|htt| htt.request(req)}
|
67
|
-
raise Tr8n::Exception.new("Synchronization failed") unless response.status == 200
|
114
|
+
# pp payload
|
68
115
|
|
69
|
-
|
116
|
+
response = Net::HTTP.start(uri.host, uri.port) do |http|
|
117
|
+
http.request(req)
|
118
|
+
end
|
119
|
+
|
120
|
+
if response.is_a?(Net::HTTPInternalServerError)
|
121
|
+
raise Exception.new("Failed to synchronize keys: #{response.body}")
|
122
|
+
end
|
123
|
+
|
124
|
+
raise Tr8n::Exception.new("Synchronization failed") unless response.is_a?(Net::HTTPOK)
|
125
|
+
|
126
|
+
data = HashWithIndifferentAccess.new(JSON.parse(response.body))
|
127
|
+
# pp data
|
128
|
+
|
129
|
+
data[:translation_keys].each do |tkey_hash|
|
130
|
+
# pp tkey_hash
|
131
|
+
Tr8n::TranslationKey.create_from_sync_hash(tkey_hash, Tr8n::Config.system_translator)
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
def self.download(opts = {})
|
136
|
+
uri = URI.parse("#{Tr8n::Config.synchronization_server}/api/application/sync")
|
137
|
+
params = {:method=>"download", :batch_size => Tr8n::Config.synchronization_batch_size}
|
138
|
+
params[:force] = true if opts[:force]
|
139
|
+
|
140
|
+
req = Net::HTTP::Post.new(uri.path)
|
141
|
+
req["Content-Type"] = "application/json"
|
142
|
+
req["Authorization"] = "Bearer #{access_token}"
|
143
|
+
req.body = params.to_json
|
144
|
+
|
145
|
+
response = Net::HTTP.start(uri.host, uri.port) do |http|
|
146
|
+
http.request(req)
|
147
|
+
end
|
148
|
+
|
149
|
+
if response.is_a?(Net::HTTPInternalServerError)
|
150
|
+
raise Exception.new("Failed to download translations: #{response.body}")
|
151
|
+
end
|
152
|
+
|
153
|
+
raise Tr8n::Exception.new("Synchronization failed") unless response.is_a?(Net::HTTPOK)
|
154
|
+
|
155
|
+
data = HashWithIndifferentAccess.new(JSON.parse(response.body))
|
156
|
+
# pp data
|
70
157
|
|
71
158
|
data[:translation_keys].each do |tkey_hash|
|
72
|
-
|
159
|
+
# pp tkey_hash
|
160
|
+
tkey, translations = Tr8n::TranslationKey.create_from_sync_hash(tkey_hash, Tr8n::Config.system_translator)
|
161
|
+
tkey.mark_as_synced!
|
73
162
|
end
|
163
|
+
|
164
|
+
data[:translation_keys].size
|
165
|
+
end
|
166
|
+
|
167
|
+
def self.log(msg)
|
168
|
+
pp "#{Time.now}: #{msg}"
|
74
169
|
end
|
75
170
|
|
76
171
|
end
|