air18n 0.1.30 → 0.1.31
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/lib/air18n/phrase_translation.rb +111 -4
- data/lib/air18n/version.rb +1 -1
- data/spec/lib/air18n/phrase_translation_spec.rb +99 -2
- metadata +2 -2
|
@@ -56,11 +56,97 @@ module Air18n
|
|
|
56
56
|
all_locales
|
|
57
57
|
end
|
|
58
58
|
|
|
59
|
+
def self.create_translation(phrase_id, phrase_key, target_locale, value, user_id, do_xss_check, allow_verification)
|
|
60
|
+
pt = PhraseTranslation.new
|
|
61
|
+
pt.user_id = user_id
|
|
62
|
+
pt.phrase_id = phrase_id
|
|
63
|
+
pt.key = phrase_key
|
|
64
|
+
pt.locale = target_locale
|
|
65
|
+
|
|
66
|
+
latest = pt.phrase.latest_translation(pt.locale)
|
|
67
|
+
|
|
68
|
+
pt.value = normalize_translation_value(value)
|
|
69
|
+
normalized_latest_value = latest.present? && normalize_translation_value(latest.value)
|
|
70
|
+
|
|
71
|
+
value_same_as_latest = pt.value == normalized_latest_value
|
|
72
|
+
|
|
73
|
+
if latest && value_same_as_latest && latest.is_stale && user_id == latest.user_id
|
|
74
|
+
latest.is_stale = false
|
|
75
|
+
if latest.save
|
|
76
|
+
response_obj = {
|
|
77
|
+
:status => 'success',
|
|
78
|
+
:message => 'Translation marked as up-to-date.',
|
|
79
|
+
:phrase_id => latest.phrase_id,
|
|
80
|
+
:key => latest.key,
|
|
81
|
+
:locale => latest.locale,
|
|
82
|
+
:value => latest.value,
|
|
83
|
+
:is_verification => latest.is_verification
|
|
84
|
+
}
|
|
85
|
+
else
|
|
86
|
+
response_obj = { :status => 'error', :message => latest.errors.values.join('; ') }
|
|
87
|
+
end
|
|
88
|
+
else
|
|
89
|
+
do_save = false
|
|
90
|
+
message = ""
|
|
91
|
+
|
|
92
|
+
safeness = XssDetector.safe?(pt.phrase.value, pt.value, I18n.default_locale, pt.locale)
|
|
93
|
+
|
|
94
|
+
if pt.value.empty?
|
|
95
|
+
message = "Translation empty; nothing saved."
|
|
96
|
+
elsif do_xss_check && !safeness[:safe]
|
|
97
|
+
message = safeness[:reason]
|
|
98
|
+
elsif (latest && value_same_as_latest) && (latest.user_id != 0)
|
|
99
|
+
if !allow_verification
|
|
100
|
+
allowed, error = false, "Verification of non-stale phrases is currently disabled in '#{pt.locale}'"
|
|
101
|
+
else
|
|
102
|
+
allowed, error = pt.verification_allowed?(latest)
|
|
103
|
+
end
|
|
104
|
+
if allowed
|
|
105
|
+
pt.is_verification = true
|
|
106
|
+
do_save = true
|
|
107
|
+
message = "Translation verified."
|
|
108
|
+
else
|
|
109
|
+
message = error
|
|
110
|
+
end
|
|
111
|
+
else
|
|
112
|
+
do_save = true
|
|
113
|
+
message = "Translation saved."
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
if do_save
|
|
117
|
+
if pt.save
|
|
118
|
+
response_obj = {
|
|
119
|
+
:status => 'success',
|
|
120
|
+
:message => message,
|
|
121
|
+
:phrase_id => pt.phrase_id,
|
|
122
|
+
:key => pt.key,
|
|
123
|
+
:locale => pt.locale,
|
|
124
|
+
:value => pt.value,
|
|
125
|
+
:is_verification => pt.is_verification
|
|
126
|
+
}
|
|
127
|
+
else
|
|
128
|
+
response_obj = {:status => 'error', :message => pt.errors.values.join('; ')}
|
|
129
|
+
end
|
|
130
|
+
else
|
|
131
|
+
response_obj = {:status => 'error', :message => message}
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
response_obj
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
# Computes monthly activity for months between opts[:since] and opts[:to].
|
|
139
|
+
# If opts[:to] is not set, starts from the current month.
|
|
140
|
+
# If opts[:since] is not set, goes back until a month in which there is no activity.
|
|
141
|
+
# If user_id is 0, computes activity for all users.
|
|
59
142
|
def self.translator_activity_data_master(user_id = 0, opts = {})
|
|
60
|
-
|
|
143
|
+
to_date = opts[:to] ? opts[:to] : Date.today
|
|
144
|
+
d = to_date.beginning_of_month
|
|
61
145
|
activities = []
|
|
62
146
|
|
|
63
147
|
while true
|
|
148
|
+
break if opts[:since] && d < opts[:since]
|
|
149
|
+
|
|
64
150
|
if d < Date.new(2012, 8, 02)
|
|
65
151
|
activity_for_month = translator_activity_data(user_id, opts.merge(:since => d, :to => (d >> 1)))
|
|
66
152
|
elsif d < Date.new(2012, 10, 02)
|
|
@@ -69,7 +155,7 @@ module Air18n
|
|
|
69
155
|
activity_for_month = translator_activity_data_v3(user_id, opts.merge(:since => d, :to => (d >> 1)))
|
|
70
156
|
end
|
|
71
157
|
|
|
72
|
-
break if
|
|
158
|
+
break if !opts[:since] && activity_for_month.blank?
|
|
73
159
|
|
|
74
160
|
activities += activity_for_month
|
|
75
161
|
d = d << 1
|
|
@@ -376,8 +462,16 @@ module Air18n
|
|
|
376
462
|
compare_to = translation_pair[:previous_translation].scan(/[[:alnum:]]+/).size
|
|
377
463
|
end
|
|
378
464
|
|
|
379
|
-
|
|
380
|
-
|
|
465
|
+
if compare_to == 0
|
|
466
|
+
proportion_translated = 0.0
|
|
467
|
+
proportion_verified = 0.0
|
|
468
|
+
|
|
469
|
+
LoggingHelper.error("Word counts for weird pair: #{translation_pair.inspect}")
|
|
470
|
+
LoggingHelper.error("distance: #{distance.inspect}, compare to: #{compare_to.inspect}")
|
|
471
|
+
else
|
|
472
|
+
proportion_translated = [distance.to_f / compare_to.to_f, 1.0].min
|
|
473
|
+
proportion_verified = 1.0 - proportion_translated
|
|
474
|
+
end
|
|
381
475
|
end
|
|
382
476
|
|
|
383
477
|
[(translation_pair[:source_word_count] * proportion_translated).round,
|
|
@@ -637,6 +731,7 @@ module Air18n
|
|
|
637
731
|
end
|
|
638
732
|
|
|
639
733
|
private
|
|
734
|
+
|
|
640
735
|
def quote_vars(vars)
|
|
641
736
|
vars.map { |var| "%{#{var}}" }.join(", ")
|
|
642
737
|
end
|
|
@@ -679,5 +774,17 @@ module Air18n
|
|
|
679
774
|
# ap dm
|
|
680
775
|
dm[input_b.length][input_a.length]
|
|
681
776
|
end
|
|
777
|
+
|
|
778
|
+
# Called on every translation before it is stored in the database.
|
|
779
|
+
# Strips whitespace from ends and makes an attempt to remove spurious HTML
|
|
780
|
+
# changes like reordering of HTML attributes within tags.
|
|
781
|
+
def self.normalize_translation_value(value)
|
|
782
|
+
# Translators like to add extra whitespace, and extra whitespace sometimes
|
|
783
|
+
# messes up things!
|
|
784
|
+
# We also remove alt="" because ckeditor likes to add that for no reason,
|
|
785
|
+
# and it adds it in different order arbitrarily which makes translations
|
|
786
|
+
# change even when they don't really change.
|
|
787
|
+
value.strip.gsub(' alt=""', '')
|
|
788
|
+
end
|
|
682
789
|
end
|
|
683
790
|
end
|
data/lib/air18n/version.rb
CHANGED
|
@@ -132,6 +132,88 @@ describe Air18n::PhraseTranslation do
|
|
|
132
132
|
end
|
|
133
133
|
end
|
|
134
134
|
|
|
135
|
+
context 'create_translation' do
|
|
136
|
+
it 'should create translations properly' do
|
|
137
|
+
phrase = FactoryGirl.create(:phrase, :key => 'create translation', :value => 'why hello there')
|
|
138
|
+
user_id_first = 9
|
|
139
|
+
user_id_second = 10
|
|
140
|
+
do_xss_check = true
|
|
141
|
+
allow_verification = true
|
|
142
|
+
|
|
143
|
+
##### 1. First translation of new phrase.
|
|
144
|
+
|
|
145
|
+
response = Air18n::PhraseTranslation.create_translation(
|
|
146
|
+
phrase.id, phrase.key, :es, 'whyo helloo thereo newword0', user_id_first, do_xss_check, allow_verification)
|
|
147
|
+
response.should include(
|
|
148
|
+
:status => 'success', :message => 'Translation saved.')
|
|
149
|
+
|
|
150
|
+
translation = phrase.latest_translation(:es)
|
|
151
|
+
translation.value.should == 'whyo helloo thereo newword0'
|
|
152
|
+
translation.key.should == 'create translation'
|
|
153
|
+
translation.source_word_count.should == 3
|
|
154
|
+
translation.source_hash.should == "2ac1a5cdad0d3ca0b0c318000c2f7378"
|
|
155
|
+
|
|
156
|
+
##### 2. Tweak to existing translation to add some words.
|
|
157
|
+
|
|
158
|
+
response = Air18n::PhraseTranslation.create_translation(
|
|
159
|
+
phrase.id, phrase.key, :es, 'whyo helloo newword1 thereo newword2', user_id_first, do_xss_check, allow_verification)
|
|
160
|
+
|
|
161
|
+
translation = phrase.latest_translation(:es)
|
|
162
|
+
translation.value.should == 'whyo helloo newword1 thereo newword2'
|
|
163
|
+
translation.source_word_count.should == 3
|
|
164
|
+
translation.source_hash.should == "2ac1a5cdad0d3ca0b0c318000c2f7378"
|
|
165
|
+
|
|
166
|
+
##### 3. Tweak to existing translation to delete a word.
|
|
167
|
+
|
|
168
|
+
response = Air18n::PhraseTranslation.create_translation(
|
|
169
|
+
phrase.id, phrase.key, :es, 'whyo helloo newword1 newword2', user_id_first, do_xss_check, allow_verification)
|
|
170
|
+
|
|
171
|
+
translation = phrase.latest_translation(:es)
|
|
172
|
+
translation.value.should == 'whyo helloo newword1 newword2'
|
|
173
|
+
translation.source_word_count.should == 3
|
|
174
|
+
translation.source_hash.should == "2ac1a5cdad0d3ca0b0c318000c2f7378"
|
|
175
|
+
|
|
176
|
+
##### 4. Submission of unchanged translation.
|
|
177
|
+
|
|
178
|
+
response = Air18n::PhraseTranslation.create_translation(
|
|
179
|
+
phrase.id, phrase.key, :es, 'whyo helloo newword1 newword2', user_id_first, do_xss_check, allow_verification)
|
|
180
|
+
response.should include(
|
|
181
|
+
:status => 'error', :message => 'You last translated this phrase, so somebody else must verify it.')
|
|
182
|
+
|
|
183
|
+
unchanged_translation = phrase.latest_translation(:es)
|
|
184
|
+
unchanged_translation.id.should == translation.id
|
|
185
|
+
|
|
186
|
+
##### 5. Submission of unchanged translation for stale phrase.
|
|
187
|
+
|
|
188
|
+
phrase.value = 'why hello there, good day'
|
|
189
|
+
phrase.save!
|
|
190
|
+
|
|
191
|
+
response = Air18n::PhraseTranslation.create_translation(
|
|
192
|
+
phrase.id, phrase.key, :es, 'whyo helloo newword1 newword2', user_id_first, do_xss_check, allow_verification)
|
|
193
|
+
response.should include(
|
|
194
|
+
:status => 'success', :message => 'Translation marked as up-to-date.')
|
|
195
|
+
|
|
196
|
+
translation = phrase.latest_translation(:es)
|
|
197
|
+
translation.value.should == 'whyo helloo newword1 newword2'
|
|
198
|
+
translation.source_word_count.should == 3
|
|
199
|
+
translation.is_verification.should == false # Not actually a verification.
|
|
200
|
+
translation.source_hash.should == "2ac1a5cdad0d3ca0b0c318000c2f7378"
|
|
201
|
+
|
|
202
|
+
##### 6. Verification by a different translator.
|
|
203
|
+
|
|
204
|
+
response = Air18n::PhraseTranslation.create_translation(
|
|
205
|
+
phrase.id, phrase.key, :es, 'whyo helloo newword1 newword2', user_id_second, do_xss_check, allow_verification)
|
|
206
|
+
response.should include(
|
|
207
|
+
:status => 'success', :message => 'Translation verified.')
|
|
208
|
+
|
|
209
|
+
translation = phrase.latest_translation(:es)
|
|
210
|
+
translation.value.should == 'whyo helloo newword1 newword2'
|
|
211
|
+
translation.source_word_count.should == 5
|
|
212
|
+
translation.is_verification.should == true
|
|
213
|
+
translation.source_hash.should == "6f20684ed3e1fa2c9ccd30249d5c9f7a"
|
|
214
|
+
end
|
|
215
|
+
end
|
|
216
|
+
|
|
135
217
|
context 'word counts' do
|
|
136
218
|
it 'should keep track of translated and verified word count in old regime' do
|
|
137
219
|
Air18n::PhraseTranslation.destroy_all
|
|
@@ -148,7 +230,7 @@ describe Air18n::PhraseTranslation do
|
|
|
148
230
|
phrase_verification = FactoryGirl.create(:phrase_translation, :phrase => @phrase2, :user_id => @user2, :is_verification => true, :created_at => Date.new(2012, 05, 04))
|
|
149
231
|
end
|
|
150
232
|
|
|
151
|
-
data = Air18n::PhraseTranslation.translator_activity_data_master
|
|
233
|
+
data = Air18n::PhraseTranslation.translator_activity_data_master(0, :since => Date.new(2012, 4, 1))
|
|
152
234
|
|
|
153
235
|
data.should include(
|
|
154
236
|
{:year=>2012,
|
|
@@ -198,7 +280,7 @@ describe Air18n::PhraseTranslation do
|
|
|
198
280
|
verification = FactoryGirl.create(:phrase_translation, :value => 'oogie boogie boo', :phrase => @phrase1, :user_id => @user1, :source_hash => 'new default text', :source_word_count => 13, :is_verification => true, :created_at => Date.new(2012, 10, 5))
|
|
199
281
|
end
|
|
200
282
|
|
|
201
|
-
data = Air18n::PhraseTranslation.translator_activity_data_master
|
|
283
|
+
data = Air18n::PhraseTranslation.translator_activity_data_master(0, :since => Date.new(2012, 9, 1))
|
|
202
284
|
|
|
203
285
|
# Example Scenario:
|
|
204
286
|
# Translator A translates phrase Y when it is 8 English words
|
|
@@ -530,6 +612,21 @@ describe Air18n::PhraseTranslation do
|
|
|
530
612
|
words_translated, words_verified = Air18n::PhraseTranslation.word_counts_from_translation_pair(pair)
|
|
531
613
|
words_translated.should == 0
|
|
532
614
|
words_verified.should == 1
|
|
615
|
+
|
|
616
|
+
pair = {
|
|
617
|
+
:translation=>".",
|
|
618
|
+
:previous_translation=>".",
|
|
619
|
+
:was_stale=>true,
|
|
620
|
+
:locale=>"pl",
|
|
621
|
+
:user_id=>2055948,
|
|
622
|
+
:previous_user_id=>1701571,
|
|
623
|
+
:datetime=>Date.new(2012, 11, 13),
|
|
624
|
+
:source_word_count=>0,
|
|
625
|
+
:phrase_key=>"helpers.label.collection.name"
|
|
626
|
+
}
|
|
627
|
+
words_translated, words_verified = Air18n::PhraseTranslation.word_counts_from_translation_pair(pair)
|
|
628
|
+
words_translated.should == 0
|
|
629
|
+
words_verified.should == 0
|
|
533
630
|
end
|
|
534
631
|
|
|
535
632
|
it 'should compute correct monthly activities' do
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: air18n
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.1.
|
|
4
|
+
version: 0.1.31
|
|
5
5
|
prerelease:
|
|
6
6
|
platform: ruby
|
|
7
7
|
authors:
|
|
@@ -13,7 +13,7 @@ authors:
|
|
|
13
13
|
autorequire:
|
|
14
14
|
bindir: bin
|
|
15
15
|
cert_chain: []
|
|
16
|
-
date: 2012-11-
|
|
16
|
+
date: 2012-11-15 00:00:00.000000000 Z
|
|
17
17
|
dependencies:
|
|
18
18
|
- !ruby/object:Gem::Dependency
|
|
19
19
|
name: i18n
|