air18n 0.1.27 → 0.1.28
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +14 -9
- data/lib/air18n/phrase_translation.rb +225 -4
- data/lib/air18n/version.rb +1 -1
- data/spec/lib/air18n/phrase_translation_spec.rb +308 -3
- metadata +2 -2
data/README.md
CHANGED
@@ -2,23 +2,28 @@
|
|
2
2
|
|
3
3
|
Rails plugin for amazing internationalization.
|
4
4
|
|
5
|
-
## Installation
|
5
|
+
## Installation and Usage
|
6
6
|
|
7
|
-
|
7
|
+
For Airbnb users, see wiki page:
|
8
|
+
https://github.com/airbnb/airbnb/wiki/How-to-internationalize-your-app
|
8
9
|
|
9
|
-
|
10
|
+
## How to add release a new version
|
10
11
|
|
11
|
-
|
12
|
+
Ask Jason, Spike, or Sri to run:
|
12
13
|
|
13
|
-
|
14
|
+
gem owner air18n --add name@example.com
|
14
15
|
|
15
|
-
|
16
|
+
where "name@example.com" is the email address associated with your
|
17
|
+
RubyGems account. If you don't have a RubyGems account, make one, and
|
18
|
+
also follow the instructions under "API Access" on the RubyGems "Edit
|
19
|
+
Profile" page.
|
16
20
|
|
17
|
-
|
21
|
+
When developing air18n you can point monorail or another app using it to
|
22
|
+
use your local changes like this in the app's Gemfile:
|
18
23
|
|
19
|
-
|
24
|
+
gem 'air18n', :path => '~/Code/air18n'
|
20
25
|
|
21
|
-
|
26
|
+
Then bundle install.
|
22
27
|
|
23
28
|
## Contributing
|
24
29
|
|
@@ -56,18 +56,20 @@ module Air18n
|
|
56
56
|
all_locales
|
57
57
|
end
|
58
58
|
|
59
|
-
def self.translator_activity_data_master(user_id = 0)
|
59
|
+
def self.translator_activity_data_master(user_id = 0, opts = {})
|
60
60
|
d = Date.today.beginning_of_month
|
61
61
|
activities = []
|
62
62
|
|
63
63
|
while true
|
64
64
|
if d < Date.new(2012, 8, 02)
|
65
|
-
activity_for_month = translator_activity_data(user_id, :since => d, :to => (d >> 1)
|
65
|
+
activity_for_month = translator_activity_data(user_id, opts.merge(:since => d, :to => (d >> 1)))
|
66
|
+
elsif d < Date.new(2012, 10, 02)
|
67
|
+
activity_for_month = translator_activity_data_new(user_id, opts.merge(:since => d, :to => (d >> 1)))
|
66
68
|
else
|
67
|
-
activity_for_month =
|
69
|
+
activity_for_month = translator_activity_data_v3(user_id, opts.merge(:since => d, :to => (d >> 1)))
|
68
70
|
end
|
69
71
|
|
70
|
-
break if activity_for_month.blank? && d
|
72
|
+
break if activity_for_month.blank? && !(user_id == 0 && d >= (Date.today - 2.years))
|
71
73
|
|
72
74
|
activities += activity_for_month
|
73
75
|
d = d << 1
|
@@ -241,6 +243,198 @@ module Air18n
|
|
241
243
|
end
|
242
244
|
end
|
243
245
|
|
246
|
+
def self.translator_activity_data_v3 user_id=0, opts={}
|
247
|
+
activity_from_translation_pairs(get_translation_pairs(user_id, opts[:since], opts[:to]), opts)
|
248
|
+
end
|
249
|
+
|
250
|
+
def self.activity_from_translation_pairs(translation_pairs, opts={})
|
251
|
+
per_user_locale_month_year =
|
252
|
+
Hash.new {|h, year| h[year] =
|
253
|
+
Hash.new {|h, month| h[month] =
|
254
|
+
Hash.new{|h, user_id| h[user_id] =
|
255
|
+
Hash.new{|h, locale| h[locale] =
|
256
|
+
Hash.new {|h, day| h[day] = {
|
257
|
+
:phrases_translated => 0,
|
258
|
+
:phrases_verified => 0,
|
259
|
+
:words_translated => 0,
|
260
|
+
:words_verified => 0,
|
261
|
+
:keys_translated => [],
|
262
|
+
:keys_verified => [],
|
263
|
+
} } } } } }
|
264
|
+
|
265
|
+
translation_pairs.each do |pair|
|
266
|
+
words_translated, words_verified = self.word_counts_from_translation_pair(pair)
|
267
|
+
sums = per_user_locale_month_year[
|
268
|
+
pair[:datetime].year][
|
269
|
+
pair[:datetime].month][
|
270
|
+
pair[:user_id]][
|
271
|
+
pair[:locale]][
|
272
|
+
pair[:datetime].day]
|
273
|
+
sums[:words_translated] += words_translated
|
274
|
+
sums[:words_verified] += words_verified
|
275
|
+
if words_verified > 0
|
276
|
+
sums[:keys_verified] << pair[:phrase_key]
|
277
|
+
sums[:phrases_verified] += 1
|
278
|
+
else
|
279
|
+
sums[:keys_translated] << pair[:phrase_key]
|
280
|
+
sums[:phrases_translated] += 1
|
281
|
+
end
|
282
|
+
end
|
283
|
+
|
284
|
+
ret = []
|
285
|
+
|
286
|
+
per_user_locale_month_year.each do |year, months|
|
287
|
+
months.each do |month, user_ids|
|
288
|
+
user_ids.each do |user_id, locales|
|
289
|
+
locales.each do |locale, days|
|
290
|
+
if opts[:daily]
|
291
|
+
days.each do |day, sums|
|
292
|
+
ret << {
|
293
|
+
:year => year,
|
294
|
+
:month => month,
|
295
|
+
:day => day,
|
296
|
+
:locale => locale,
|
297
|
+
:user_id => user_id,
|
298
|
+
:activity => {
|
299
|
+
:num_translations => sums[:phrases_translated],
|
300
|
+
:num_verifications => sums[:phrases_verified],
|
301
|
+
:word_count_translations => sums[:words_translated],
|
302
|
+
:word_count_verifications => sums[:words_verified],
|
303
|
+
:translated_keys => sums[:keys_translated],
|
304
|
+
:verified_keys => sums[:keys_verified],
|
305
|
+
}
|
306
|
+
}
|
307
|
+
LoggingHelper.info("keys translated: #{sums[:keys_translated]}")
|
308
|
+
LoggingHelper.info("keys verified: #{sums[:keys_verified]}")
|
309
|
+
LoggingHelper.info("ret now: #{ret.inspect}")
|
310
|
+
end
|
311
|
+
else
|
312
|
+
monthly_sums = {
|
313
|
+
:phrases_translated => 0,
|
314
|
+
:phrases_verified => 0,
|
315
|
+
:words_translated => 0,
|
316
|
+
:words_verified => 0,
|
317
|
+
:keys_translated => [],
|
318
|
+
:keys_verified => [],
|
319
|
+
}
|
320
|
+
days.each do |day, sums|
|
321
|
+
sums.keys.each do |key|
|
322
|
+
monthly_sums[key] += sums[key]
|
323
|
+
end
|
324
|
+
end
|
325
|
+
ret << {
|
326
|
+
:year => year,
|
327
|
+
:month => month,
|
328
|
+
:locale => locale,
|
329
|
+
:user_id => user_id,
|
330
|
+
:activity => {
|
331
|
+
:num_translations => monthly_sums[:phrases_translated],
|
332
|
+
:num_verifications => monthly_sums[:phrases_verified],
|
333
|
+
:word_count_translations => monthly_sums[:words_translated],
|
334
|
+
:word_count_verifications => monthly_sums[:words_verified],
|
335
|
+
:translated_keys => monthly_sums[:keys_translated],
|
336
|
+
:verified_keys => monthly_sums[:keys_verified],
|
337
|
+
}
|
338
|
+
}
|
339
|
+
LoggingHelper.info("keys translated: #{monthly_sums[:keys_translated]}")
|
340
|
+
LoggingHelper.info("keys verified: #{monthly_sums[:keys_verified]}")
|
341
|
+
LoggingHelper.info("ret now: #{ret.inspect}")
|
342
|
+
end
|
343
|
+
end
|
344
|
+
end
|
345
|
+
end
|
346
|
+
end
|
347
|
+
|
348
|
+
ret
|
349
|
+
end
|
350
|
+
|
351
|
+
SPACELESS_LANGUAGES = [:ja, :zh, :th]
|
352
|
+
|
353
|
+
def self.word_counts_from_translation_pair(translation_pair)
|
354
|
+
if translation_pair[:previous_translation].blank?
|
355
|
+
proportion_translated = 1.0
|
356
|
+
proportion_verified = 0.0
|
357
|
+
elsif !translation_pair[:was_stale] &&
|
358
|
+
translation_pair[:previous_user_id] == translation_pair[:user_id]
|
359
|
+
# Don't pay anything for retranslating your own work.
|
360
|
+
proportion_translated = 0.0
|
361
|
+
proportion_verified = 0.0
|
362
|
+
else
|
363
|
+
# Pay for translating changed/added parts, and for verifying the rest.
|
364
|
+
|
365
|
+
if SPACELESS_LANGUAGES.include?(translation_pair[:locale].to_sym)
|
366
|
+
distance = levenshtein_distance_fast(
|
367
|
+
translation_pair[:previous_translation].split(''),
|
368
|
+
translation_pair[:translation].split(''),
|
369
|
+
1, 0, 1)
|
370
|
+
compare_to = translation_pair[:previous_translation].size
|
371
|
+
else
|
372
|
+
distance = levenshtein_distance_fast(
|
373
|
+
PhraseTranslation.segment(translation_pair[:previous_translation]),
|
374
|
+
PhraseTranslation.segment(translation_pair[:translation]),
|
375
|
+
1, 0, 1)
|
376
|
+
compare_to = PhraseTranslation.segment(translation_pair[:previous_translation]).size
|
377
|
+
end
|
378
|
+
|
379
|
+
proportion_translated = [distance.to_f / compare_to.to_f, 1.0].min
|
380
|
+
proportion_verified = 1.0 - proportion_translated
|
381
|
+
end
|
382
|
+
|
383
|
+
[(translation_pair[:source_word_count] * proportion_translated).round,
|
384
|
+
(translation_pair[:source_word_count] * proportion_verified).round]
|
385
|
+
end
|
386
|
+
|
387
|
+
def self.get_translation_pairs(user_id, from_date, to_date)
|
388
|
+
ret = []
|
389
|
+
Phrase.select(:id).find_in_batches do |batch|
|
390
|
+
pt_scope = PhraseTranslation.where(:phrase_id => batch)
|
391
|
+
if from_date
|
392
|
+
pt_scope = pt_scope.where("created_at >= ?", from_date)
|
393
|
+
end
|
394
|
+
if to_date
|
395
|
+
pt_scope = pt_scope.where("created_at < ?", to_date)
|
396
|
+
end
|
397
|
+
if user_id != 0
|
398
|
+
pt_scope = pt_scope.where(:user_id => user_id)
|
399
|
+
end
|
400
|
+
phrase_to_phrase_translations = pt_scope.all.group_by { |pt| [pt.locale, pt.phrase_id] }
|
401
|
+
phrase_to_phrase_translations.each do |(locale, phrase_id), phrase_translations|
|
402
|
+
phrase_translations.sort_by! { |pt| pt.created_at }
|
403
|
+
LoggingHelper.info "Getting previous translation!"
|
404
|
+
previous_translation = PhraseTranslation.where("created_at < ?", phrase_translations.first.created_at - 1.second).
|
405
|
+
where(:locale => locale).
|
406
|
+
where(:phrase_id => phrase_id).
|
407
|
+
order("created_at DESC").
|
408
|
+
first
|
409
|
+
phrase_translations.each do |pt|
|
410
|
+
if previous_translation
|
411
|
+
previous_translation_text = previous_translation.value
|
412
|
+
previous_translation_user_id = previous_translation.user_id
|
413
|
+
was_stale = previous_translation.source_hash != pt.source_hash
|
414
|
+
else
|
415
|
+
previous_translation_text = ''
|
416
|
+
previous_translation_user_id = 0
|
417
|
+
was_stale = false
|
418
|
+
end
|
419
|
+
translation_text = pt.value
|
420
|
+
ret << { :translation => translation_text,
|
421
|
+
:previous_translation => previous_translation_text,
|
422
|
+
:was_stale => was_stale,
|
423
|
+
:locale => pt.locale,
|
424
|
+
:user_id => pt.user_id,
|
425
|
+
:previous_user_id => previous_translation_user_id,
|
426
|
+
:datetime => pt.created_at,
|
427
|
+
:source_word_count => pt.source_word_count,
|
428
|
+
:phrase_key => pt.key,
|
429
|
+
}
|
430
|
+
|
431
|
+
previous_translation = pt
|
432
|
+
end
|
433
|
+
end
|
434
|
+
end
|
435
|
+
ret
|
436
|
+
end
|
437
|
+
|
244
438
|
# Helper method for translator_activity_data which counts words and payment
|
245
439
|
# of a set of translations and verifications, in ((is verification bool => (phrase id =>
|
246
440
|
# source word count map)).
|
@@ -457,5 +651,32 @@ module Air18n
|
|
457
651
|
def self.created_at_year_sql
|
458
652
|
Air18n::PhraseTranslation.connection.adapter_name.starts_with?('Mysql') ? 'YEAR(pt.created_at)' : 'STRFTIME("%Y", pt.created_at)'
|
459
653
|
end
|
654
|
+
|
655
|
+
# Unlike Text::Levenshtein, this supports passing in arrays, which is useful
|
656
|
+
# for computing word-level levenshtein distance.
|
657
|
+
def self.levenshtein_distance_fast(input_a, input_b, insertion_penalty=1, deletion_penalty=1, substitution_penalty=1)
|
658
|
+
return nil if input_a.nil? || input_b.nil?
|
659
|
+
|
660
|
+
dm = []
|
661
|
+
dm[0] = (0..input_a.length).collect { |i| i * deletion_penalty}
|
662
|
+
fill = [0] * (input_a.length)
|
663
|
+
|
664
|
+
for i in 1..input_b.length
|
665
|
+
dm[i] = [i * insertion_penalty] + fill
|
666
|
+
end
|
667
|
+
|
668
|
+
for i in 1..input_b.length
|
669
|
+
for j in 1..input_a.length
|
670
|
+
dm[i][j] = [
|
671
|
+
dm[i-1][j-1] + (input_a[j-1] == input_b[i-1] ? 0 : substitution_penalty),
|
672
|
+
dm[i][j-1] + deletion_penalty,
|
673
|
+
dm[i-1][j] + insertion_penalty
|
674
|
+
].min
|
675
|
+
end
|
676
|
+
end
|
677
|
+
|
678
|
+
# ap dm
|
679
|
+
dm[input_b.length][input_a.length]
|
680
|
+
end
|
460
681
|
end
|
461
682
|
end
|
data/lib/air18n/version.rb
CHANGED
@@ -46,7 +46,7 @@ describe Air18n::PhraseTranslation do
|
|
46
46
|
|
47
47
|
it 'Should check variables' do
|
48
48
|
phrase = FactoryGirl.create(:phrase, :key => 'phone_number_not_revealed_time_zone', :value => "Your number won't be revealed. They can only call from 9am to 9pm %{time_zone}.")
|
49
|
-
translation = FactoryGirl.create(:phrase_translation, :phrase => phrase, :key => 'phone_number_not_revealed_time_zone', :value => '您的電話號碼將不會被透露。他們只能在上午9時至晚上9時%{time_zone}這段時間給您致電')
|
49
|
+
translation = FactoryGirl.create(:phrase_translation, :locale => :zh, :phrase => phrase, :key => 'phone_number_not_revealed_time_zone', :value => '您的電話號碼將不會被透露。他們只能在上午9時至晚上9時%{time_zone}這段時間給您致電')
|
50
50
|
validated = translation.save
|
51
51
|
validated.should == true
|
52
52
|
translation.value = '您的電話號碼將不會被透露。他們只能在上午9時至晚上9時%{time_zon}這段時間給您致電'
|
@@ -133,7 +133,9 @@ describe Air18n::PhraseTranslation do
|
|
133
133
|
end
|
134
134
|
|
135
135
|
context 'word counts' do
|
136
|
-
it 'should keep track of translated and verified word count in
|
136
|
+
it 'should keep track of translated and verified word count in old regime' do
|
137
|
+
Air18n::PhraseTranslation.destroy_all
|
138
|
+
|
137
139
|
@user1 = 1
|
138
140
|
@user2 = 2
|
139
141
|
@phrase1 = FactoryGirl.create(:phrase, :key => 'old regime key 1', :value => 'two words')
|
@@ -177,6 +179,8 @@ describe Air18n::PhraseTranslation do
|
|
177
179
|
|
178
180
|
|
179
181
|
it 'should keep track of translated and verified word count in new regime' do
|
182
|
+
Air18n::PhraseTranslation.destroy_all
|
183
|
+
|
180
184
|
@user1 = 3
|
181
185
|
@user2 = 4
|
182
186
|
@phrase1 = FactoryGirl.create(:phrase, :key => 'new regime payments key 1', :value => 'two words')
|
@@ -240,6 +244,307 @@ describe Air18n::PhraseTranslation do
|
|
240
244
|
:verified_keys=>[["new regime payments key 1", "38a72c02b5febe4975fdcf19d32e412d"]]}
|
241
245
|
)
|
242
246
|
end
|
243
|
-
end
|
244
247
|
|
248
|
+
context 'v3 regime' do
|
249
|
+
before :all do
|
250
|
+
Air18n::PhraseTranslation.destroy_all
|
251
|
+
|
252
|
+
@user1 = 5
|
253
|
+
@user2 = 6
|
254
|
+
|
255
|
+
@phrase1 = FactoryGirl.create(:phrase, :key => 'v3 regime payments key 1', :value => "one two three four five six seven eight")
|
256
|
+
@phrase2 = FactoryGirl.create(:phrase, :key => 'v3 regime payments key 2', :value => "one two three four five six")
|
257
|
+
|
258
|
+
without_timestamping_of Air18n::PhraseTranslation do
|
259
|
+
##### Phrase 1
|
260
|
+
|
261
|
+
# User 1 translates Phrase 1 for the first time [edit of untranslated phrase]
|
262
|
+
translation = FactoryGirl.create(:phrase_translation, :locale => :de, :value => 'urgle burgle', :phrase => @phrase1, :user_id => @user1, :created_at => Date.new(2012, 11, 1))
|
263
|
+
|
264
|
+
# User 1 tweaks his own translation of Phrase 1. [edit of own unverified translation]
|
265
|
+
translation_tweak = FactoryGirl.create(:phrase_translation, :locale => :de, :value => 'urgle burgle boo', :phrase => @phrase1, :user_id => @user1, :created_at => Date.new(2012, 11, 2))
|
266
|
+
|
267
|
+
# User 2 verifies translation of Phrase 1. [pure verification]
|
268
|
+
verification_by_other_user = FactoryGirl.create(:phrase_translation, :locale => :de, :value => 'urgle burgle boo', :phrase => @phrase1, :user_id => @user2, :is_verification => true, :created_at => Date.new(2012, 11, 3))
|
269
|
+
|
270
|
+
# Phrase 1 becomes stale.
|
271
|
+
@phrase1.value = "one two three four five six seven eight nine ten eleven twelve thirteen"
|
272
|
+
@phrase1.save!
|
273
|
+
|
274
|
+
# User 1 reedits translation of Phrase 1. [edit of other's stale translation]
|
275
|
+
retranslation = FactoryGirl.create(:phrase_translation, :locale => :de, :value => 'oogie boogie boo', :phrase => @phrase1, :user_id => @user1, :created_at => Date.new(2012, 11, 4))
|
276
|
+
|
277
|
+
# User 2 pure-verifies of Phrase 1.
|
278
|
+
other_user_translation = FactoryGirl.create(:phrase_translation, :locale => :de, :value => 'oogie boogie boo', :is_verification => true, :phrase => @phrase1, :user_id => @user2, :created_at => Date.new(2012, 11, 5))
|
279
|
+
|
280
|
+
# User 1 edits verified translation of Phrase 1. [edit of other's verified translation]
|
281
|
+
verification = FactoryGirl.create(:phrase_translation, :locale => :de, :value => 'oogie booooogie boo hat ogl', :phrase => @phrase1, :user_id => @user1, :is_verification => true, :created_at => Date.new(2012, 11, 6))
|
282
|
+
|
283
|
+
##### Phrase 2
|
284
|
+
|
285
|
+
# User 1 translates Phrase 2 for the first time, but earlier in the year.
|
286
|
+
translation = FactoryGirl.create(:phrase_translation, :locale => :de, :value => 'The only verdict is vengence', :phrase => @phrase2, :user_id => @user1, :created_at => Date.new(2012, 9, 1))
|
287
|
+
|
288
|
+
# User 1 tweaks their own translation later.
|
289
|
+
translation = FactoryGirl.create(:phrase_translation, :locale => :de, :value => 'The only verdict is VENGENCE', :phrase => @phrase2, :user_id => @user1, :created_at => Date.new(2012, 11, 1))
|
290
|
+
|
291
|
+
# User 2 tweaks translation of Phrase 2.
|
292
|
+
translation = FactoryGirl.create(:phrase_translation, :locale => :de, :value => 'a vendetta, held as a votive', :is_verification => true, :phrase => @phrase2, :user_id => @user2, :created_at => Date.new(2012, 11, 2))
|
293
|
+
|
294
|
+
# User 2 tweaks their own translation of Phrase 2. [edit of own verified translation]
|
295
|
+
translation = FactoryGirl.create(:phrase_translation, :locale => :de, :value => 'a vendetta, held as a votive, not in vain', :is_verification => true, :phrase => @phrase2, :user_id => @user2, :created_at => Date.new(2012, 11, 3))
|
296
|
+
|
297
|
+
# Phrase 2 becomes stale.
|
298
|
+
@phrase2.value = "one two three four five six seven eight nine"
|
299
|
+
@phrase2.save!
|
300
|
+
|
301
|
+
# User 2 verifies existing translation of Phrase 2. [pure verification of own stale translation]
|
302
|
+
translation = FactoryGirl.create(:phrase_translation, :locale => :de, :value => 'a vendetta, held as a votive, not in vain', :is_verification => true, :phrase => @phrase2, :user_id => @user2, :created_at => Date.new(2012, 11, 4))
|
303
|
+
|
304
|
+
# Phrase 2 becomes stale again.
|
305
|
+
@phrase2.value = "one two three"
|
306
|
+
@phrase2.save!
|
307
|
+
|
308
|
+
# User 2 tweaks translation of Phrase 2. [pure verification of own stale translation]
|
309
|
+
translation = FactoryGirl.create(:phrase_translation, :locale => :de, :value => 'a vendetta, held as a votive, not in vain, for the value and veracity of such shall one day vindicate the vigilant and the virtuous', :is_verification => true, :phrase => @phrase2, :user_id => @user2, :created_at => Date.new(2012, 11, 5))
|
310
|
+
end
|
311
|
+
|
312
|
+
@golden_pairs =
|
313
|
+
[{:translation=>"urgle burgle",
|
314
|
+
:previous_translation=>"",
|
315
|
+
:was_stale=>false,
|
316
|
+
:locale=>"de",
|
317
|
+
:user_id=>5,
|
318
|
+
:previous_user_id=>0,
|
319
|
+
:datetime=>Date.new(2012, 11, 01),
|
320
|
+
:source_word_count=>8,
|
321
|
+
:phrase_key=>'v3 regime payments key 1'},
|
322
|
+
{:translation=>"urgle burgle boo",
|
323
|
+
:previous_translation=>"urgle burgle",
|
324
|
+
:was_stale=>false,
|
325
|
+
:locale=>"de",
|
326
|
+
:user_id=>5,
|
327
|
+
:previous_user_id=>5,
|
328
|
+
:datetime=>Date.new(2012, 11, 02),
|
329
|
+
:source_word_count=>8,
|
330
|
+
:phrase_key=>'v3 regime payments key 1'},
|
331
|
+
{:translation=>"urgle burgle boo",
|
332
|
+
:previous_translation=>"urgle burgle boo",
|
333
|
+
:was_stale=>false,
|
334
|
+
:locale=>"de",
|
335
|
+
:user_id=>6,
|
336
|
+
:previous_user_id=>5,
|
337
|
+
:datetime=>Date.new(2012, 11, 03),
|
338
|
+
:source_word_count=>8,
|
339
|
+
:phrase_key=>'v3 regime payments key 1'},
|
340
|
+
{:translation=>"oogie boogie boo",
|
341
|
+
:previous_translation=>"urgle burgle boo",
|
342
|
+
:was_stale=>true,
|
343
|
+
:locale=>"de",
|
344
|
+
:user_id=>5,
|
345
|
+
:previous_user_id=>6,
|
346
|
+
:datetime=>Date.new(2012, 11, 04),
|
347
|
+
:source_word_count=>13,
|
348
|
+
:phrase_key=>'v3 regime payments key 1'},
|
349
|
+
{:translation=>"oogie boogie boo",
|
350
|
+
:previous_translation=>"oogie boogie boo",
|
351
|
+
:was_stale=>false,
|
352
|
+
:locale=>"de",
|
353
|
+
:user_id=>6,
|
354
|
+
:previous_user_id=>5,
|
355
|
+
:datetime=>Date.new(2012, 11, 05),
|
356
|
+
:source_word_count=>13,
|
357
|
+
:phrase_key=>'v3 regime payments key 1'},
|
358
|
+
{:translation=>"oogie booooogie boo hat ogl",
|
359
|
+
:previous_translation=>"oogie boogie boo",
|
360
|
+
:was_stale=>false,
|
361
|
+
:locale=>"de",
|
362
|
+
:user_id=>5,
|
363
|
+
:previous_user_id=>6,
|
364
|
+
:datetime=>Date.new(2012, 11, 06),
|
365
|
+
:source_word_count=>13,
|
366
|
+
:phrase_key=>'v3 regime payments key 1'},
|
367
|
+
{:translation=>"The only verdict is VENGENCE",
|
368
|
+
:previous_translation=>"The only verdict is vengence",
|
369
|
+
:was_stale=>false,
|
370
|
+
:locale=>"de",
|
371
|
+
:user_id=>5,
|
372
|
+
:previous_user_id=>5,
|
373
|
+
:datetime=>Date.new(2012, 11, 01),
|
374
|
+
:source_word_count=>6,
|
375
|
+
:phrase_key=>'v3 regime payments key 2'},
|
376
|
+
{:translation=>"a vendetta, held as a votive",
|
377
|
+
:previous_translation=>"The only verdict is VENGENCE",
|
378
|
+
:was_stale=>false,
|
379
|
+
:locale=>"de",
|
380
|
+
:user_id=>6,
|
381
|
+
:previous_user_id=>5,
|
382
|
+
:datetime=>Date.new(2012, 11, 02),
|
383
|
+
:source_word_count=>6,
|
384
|
+
:phrase_key=>'v3 regime payments key 2'},
|
385
|
+
{:translation=>"a vendetta, held as a votive, not in vain",
|
386
|
+
:previous_translation=>"a vendetta, held as a votive",
|
387
|
+
:was_stale=>false,
|
388
|
+
:locale=>"de",
|
389
|
+
:user_id=>6,
|
390
|
+
:previous_user_id=>6,
|
391
|
+
:datetime=>Date.new(2012, 11, 03),
|
392
|
+
:source_word_count=>6,
|
393
|
+
:phrase_key=>'v3 regime payments key 2'},
|
394
|
+
{:translation=>"a vendetta, held as a votive, not in vain",
|
395
|
+
:previous_translation=>"a vendetta, held as a votive, not in vain",
|
396
|
+
:was_stale=>true,
|
397
|
+
:locale=>"de",
|
398
|
+
:user_id=>6,
|
399
|
+
:previous_user_id=>6,
|
400
|
+
:datetime=>Date.new(2012, 11, 04),
|
401
|
+
:source_word_count=>9,
|
402
|
+
:phrase_key=>'v3 regime payments key 2'},
|
403
|
+
{:translation=>
|
404
|
+
"a vendetta, held as a votive, not in vain, for the value and veracity of such shall one day vindicate the vigilant and the virtuous",
|
405
|
+
:previous_translation=>"a vendetta, held as a votive, not in vain",
|
406
|
+
:was_stale=>true,
|
407
|
+
:locale=>"de",
|
408
|
+
:user_id=>6,
|
409
|
+
:previous_user_id=>6,
|
410
|
+
:datetime=>Date.new(2012, 11, 05),
|
411
|
+
:source_word_count=>3,
|
412
|
+
:phrase_key=>'v3 regime payments key 2'}]
|
413
|
+
|
414
|
+
@golden_word_counts =
|
415
|
+
[[8, 0], # "" -> "urgle burgle", user 5
|
416
|
+
[0, 0], # same translator making an edit, user 5
|
417
|
+
[0, 8], # different translator verifying, user 6
|
418
|
+
[9, 4], # edit of stale phrase; 2/3 words changed, 2/3*13=8.66; rounds to 9, user 5
|
419
|
+
[0, 13], # verification, user 6
|
420
|
+
[13, 0], # 3 new words; 3/3 words changed, 3/3*13=13, user 5
|
421
|
+
[0, 0], # Tweak of a translation that was done in past by same person, user 5
|
422
|
+
[6, 0], # Rewrite of translation by other person, user 6
|
423
|
+
[0, 0], # Tweak by same person, user 6
|
424
|
+
[0, 9], # Pure verification of stale, user 6
|
425
|
+
[3, 0]] # Edit of stale, user 6
|
426
|
+
|
427
|
+
@golden_monthly_activity =
|
428
|
+
[{:year=>2012,
|
429
|
+
:month=>11,
|
430
|
+
:locale=>"de",
|
431
|
+
:user_id=>5,
|
432
|
+
:activity=>
|
433
|
+
{:num_translations=>4,
|
434
|
+
:num_verifications=>1,
|
435
|
+
:word_count_translations=>30,
|
436
|
+
:word_count_verifications=>4,
|
437
|
+
:translated_keys=>["v3 regime payments key 1",
|
438
|
+
"v3 regime payments key 2",
|
439
|
+
"v3 regime payments key 1",
|
440
|
+
"v3 regime payments key 1"],
|
441
|
+
:verified_keys=>["v3 regime payments key 1"]}},
|
442
|
+
{:year=>2012,
|
443
|
+
:month=>11,
|
444
|
+
:locale=>"de",
|
445
|
+
:user_id=>6,
|
446
|
+
:activity=>
|
447
|
+
{:num_translations=>3,
|
448
|
+
:num_verifications=>3,
|
449
|
+
:word_count_translations=>9,
|
450
|
+
:word_count_verifications=>30,
|
451
|
+
:translated_keys=>["v3 regime payments key 2",
|
452
|
+
"v3 regime payments key 2",
|
453
|
+
"v3 regime payments key 2"],
|
454
|
+
:verified_keys=>["v3 regime payments key 1",
|
455
|
+
"v3 regime payments key 1",
|
456
|
+
"v3 regime payments key 2"]}}]
|
457
|
+
end
|
458
|
+
|
459
|
+
it 'should be able to enumerate pairs of translations' do
|
460
|
+
pairs = Air18n::PhraseTranslation.get_translation_pairs(0, Date.new(2012, 11, 1), Date.new(2012, 12, 1))
|
461
|
+
pairs = pairs.select { |pair| pair[:locale] == 'de' }
|
462
|
+
|
463
|
+
pairs.should == @golden_pairs
|
464
|
+
end
|
465
|
+
|
466
|
+
it 'should be able to count words proportional to amount of translation edited' do
|
467
|
+
word_counts = @golden_pairs.map { |pair| Air18n::PhraseTranslation.word_counts_from_translation_pair(pair) }
|
468
|
+
word_counts.should == @golden_word_counts
|
469
|
+
end
|
470
|
+
|
471
|
+
it 'should be able to count words proportional to amount of translation edited for spaceless languages' do
|
472
|
+
pair = {
|
473
|
+
:translation=>"泊まりたいところ、きっと見つかる。",
|
474
|
+
:previous_translation=>"いいところに泊まろう。",
|
475
|
+
:was_stale=>false,
|
476
|
+
:locale=>"ja",
|
477
|
+
:user_id=>5,
|
478
|
+
:previous_user_id=>6,
|
479
|
+
:datetime=>Date.new(2012, 11, 04),
|
480
|
+
:source_word_count=>5,
|
481
|
+
:phrase_key=>'find_a_place_to_stay'
|
482
|
+
}
|
483
|
+
words_translated, words_verified = Air18n::PhraseTranslation.word_counts_from_translation_pair(pair)
|
484
|
+
words_translated.should == 5
|
485
|
+
words_verified.should == 0
|
486
|
+
|
487
|
+
pair = {
|
488
|
+
:translation=>"泊まりたいところ、きっと見つかる。",
|
489
|
+
:previous_translation=>"泊まりたいところがきっと見つかる。",
|
490
|
+
:was_stale=>false,
|
491
|
+
:locale=>"ja",
|
492
|
+
:user_id=>5,
|
493
|
+
:previous_user_id=>6,
|
494
|
+
:datetime=>Date.new(2012, 11, 04),
|
495
|
+
:source_word_count=>5,
|
496
|
+
:phrase_key=>'find_a_place_to_stay'
|
497
|
+
}
|
498
|
+
words_translated, words_verified = Air18n::PhraseTranslation.word_counts_from_translation_pair(pair)
|
499
|
+
words_translated.should == 0 # 1 / 17 * 5 = 0.29, rounds to 0
|
500
|
+
words_verified.should == 5
|
501
|
+
|
502
|
+
pair = {
|
503
|
+
:translation=>"泊まりたい所、見つかるよ。",
|
504
|
+
:previous_translation=>"泊まりたいところがきっと見つかる。こういうテキストも有り!",
|
505
|
+
:was_stale=>false,
|
506
|
+
:locale=>"ja",
|
507
|
+
:user_id=>5,
|
508
|
+
:previous_user_id=>6,
|
509
|
+
:datetime=>Date.new(2012, 11, 04),
|
510
|
+
:source_word_count=>5,
|
511
|
+
:phrase_key=>'find_a_place_to_stay'
|
512
|
+
}
|
513
|
+
words_translated, words_verified = Air18n::PhraseTranslation.word_counts_from_translation_pair(pair)
|
514
|
+
words_translated.should == 1 # 3 / 17 * 5 = 0.88, rounds to 1
|
515
|
+
words_verified.should == 4
|
516
|
+
end
|
517
|
+
|
518
|
+
it 'should compute correct monthly activities' do
|
519
|
+
data = Air18n::PhraseTranslation.activity_from_translation_pairs(@golden_pairs, :daily => false)
|
520
|
+
|
521
|
+
data.should == @golden_monthly_activity
|
522
|
+
end
|
523
|
+
|
524
|
+
it 'should compute correct daily activities' do
|
525
|
+
data = Air18n::PhraseTranslation.activity_from_translation_pairs(@golden_pairs, :daily => true)
|
526
|
+
|
527
|
+
data.first.should ==
|
528
|
+
{:year=>2012,
|
529
|
+
:month=>11,
|
530
|
+
:day=>1,
|
531
|
+
:locale=>"de",
|
532
|
+
:user_id=>5,
|
533
|
+
:activity=>
|
534
|
+
{:num_translations=>2,
|
535
|
+
:num_verifications=>0,
|
536
|
+
:word_count_translations=>8,
|
537
|
+
:word_count_verifications=>0,
|
538
|
+
:translated_keys=>["v3 regime payments key 1", "v3 regime payments key 2"],
|
539
|
+
:verified_keys=>[]}}
|
540
|
+
end
|
541
|
+
|
542
|
+
it 'should keep track of translated and verified word count in v3 regime' do
|
543
|
+
data = Air18n::PhraseTranslation.translator_activity_data_master
|
544
|
+
@golden_monthly_activity.each do |a|
|
545
|
+
data.should include(a)
|
546
|
+
end
|
547
|
+
end
|
548
|
+
end
|
549
|
+
end
|
245
550
|
end
|
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.28
|
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-10 00:00:00.000000000 Z
|
17
17
|
dependencies:
|
18
18
|
- !ruby/object:Gem::Dependency
|
19
19
|
name: i18n
|