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.
@@ -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
- d = Date.today.beginning_of_month
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 activity_for_month.blank? && !(user_id == 0 && d >= (Date.today - 2.years))
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
- proportion_translated = [distance.to_f / compare_to.to_f, 1.0].min
380
- proportion_verified = 1.0 - proportion_translated
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
@@ -1,3 +1,3 @@
1
1
  module Air18n
2
- VERSION = "0.1.30"
2
+ VERSION = "0.1.31"
3
3
  end
@@ -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.30
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-14 00:00:00.000000000 Z
16
+ date: 2012-11-15 00:00:00.000000000 Z
17
17
  dependencies:
18
18
  - !ruby/object:Gem::Dependency
19
19
  name: i18n