air18n 0.1.6 → 0.1.7
Sign up to get free protection for your applications and to get access to all the features.
- data/lib/air18n/backend.rb +0 -1
- data/lib/air18n/class_methods.rb +10 -1
- data/lib/air18n/phrase_translation.rb +97 -75
- data/lib/air18n/priority.rb +3 -0
- data/lib/air18n/version.rb +1 -1
- data/lib/generators/air18n/migration/templates/active_record/migration.rb +2 -0
- data/make_gem +1 -1
- data/spec/lib/air18n/air18n_spec.rb +5 -0
- data/spec/lib/air18n/phrase_translation_spec.rb +11 -0
- data/spec/spec_helper.rb +18 -0
- metadata +2 -2
data/lib/air18n/backend.rb
CHANGED
@@ -165,7 +165,6 @@ module Air18n
|
|
165
165
|
# the 'value' column reflects the latest English default text.
|
166
166
|
store_default = true
|
167
167
|
if result.present?
|
168
|
-
LoggingHelper.info "Default #{locale} text for key '#{key}' changed! Old default '#{result}' != new default '#{default}'. Route context: #{options[:routes_context]}"
|
169
168
|
if @default_text_change_observer.present?
|
170
169
|
@default_text_change_observer.default_text_changed(locale, key, result, default)
|
171
170
|
store_default = @default_text_change_observer.allow_default_text_change?(locale, key, result, default)
|
data/lib/air18n/class_methods.rb
CHANGED
@@ -95,11 +95,13 @@ module Air18n
|
|
95
95
|
end
|
96
96
|
|
97
97
|
# Returns list of keys looked up today or in the last week.
|
98
|
+
# If we don't have data on phrase usage, returns all keys.
|
98
99
|
def still_used_keys
|
99
100
|
still_used_keys_hash.keys
|
100
101
|
end
|
101
102
|
|
102
103
|
# Returns list of phrase IDs that are still used.
|
104
|
+
# Returns empty list if we don't have data on phrase usage.
|
103
105
|
def still_used_phrase_ids
|
104
106
|
@still_used_phrase_ids ||= begin
|
105
107
|
key_to_phrase_id = {}
|
@@ -115,6 +117,13 @@ module Air18n
|
|
115
117
|
end
|
116
118
|
end
|
117
119
|
|
120
|
+
# Returns whether or not a phrase key is still used.
|
121
|
+
# If we don't have data on phrase usage, then assumes all keys are still
|
122
|
+
# used.
|
123
|
+
def still_used?(key)
|
124
|
+
still_used_keys_hash.empty? || still_used_keys_hash.include?(key)
|
125
|
+
end
|
126
|
+
|
118
127
|
# Phrases that are from user-generated content (like hosting names) have a
|
119
128
|
# key prefixed with "ugc.".
|
120
129
|
def phrase_key_is_ugc?(key)
|
@@ -125,7 +134,7 @@ module Air18n
|
|
125
134
|
# A key needs to be translated if it is either UGC or was looked up by t()
|
126
135
|
# today or in the last week.
|
127
136
|
def needs_translation?(key)
|
128
|
-
phrase_key_is_ugc?(key) ||
|
137
|
+
phrase_key_is_ugc?(key) || still_used?(key)
|
129
138
|
end
|
130
139
|
|
131
140
|
# Create Air18n backend with given default_locale and translation prioritizer.
|
@@ -122,95 +122,117 @@ module Air18n
|
|
122
122
|
end
|
123
123
|
end
|
124
124
|
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
125
|
+
def self.translator_activity_data_new user_id=0, opts={}
|
126
|
+
user_criterion = user_id > 0 ? "AND pt.user_id=#{user_id}" : "AND NOT pt.user_id=0"
|
127
|
+
since_criterion = "AND pt.created_at >= '#{opts[:since].to_formatted_s(:db)}}'" if opts[:since]
|
128
|
+
sql = "SELECT year(pt.created_at) year, month(pt.created_at) month, pt.user_id user_id, pt.locale locale, day(pt.created_at) day, p.id phrase_id, pt.source_word_count source_word_count, pt.source_hash source_hash, pt.is_verification FROM phrase_translations pt, phrases p WHERE pt.phrase_id=p.id #{user_criterion} #{since_criterion} GROUP BY year(pt.created_at), month(pt.created_at), pt.user_id, pt.locale, pt.phrase_id, pt.source_hash, pt.is_verification"
|
129
|
+
res = self.connection.select_rows(sql)
|
130
|
+
phrases_per_user_locale_month_year =
|
131
|
+
Hash.new {|h, year| h[year] =
|
132
|
+
Hash.new {|h, month| h[month] =
|
133
|
+
Hash.new{|h, user_id| h[user_id] =
|
134
|
+
Hash.new{|h, locale| h[locale] =
|
135
|
+
Hash.new {|h, day| h[day] =
|
136
|
+
Hash.new {|h, is_verification| h[is_verification] =
|
137
|
+
Hash.new
|
138
|
+
} } } } } }
|
139
|
+
|
140
|
+
res.each do |row|
|
141
|
+
year, month, user_id, locale, day, phrase_id, source_word_count, source_hash, is_verification = row
|
142
|
+
is_verification = (is_verification == 1)
|
143
|
+
phrases_per_user_locale_month_year[year][month][user_id][locale][day][is_verification].merge!({source_hash => source_word_count})
|
132
144
|
end
|
133
|
-
end
|
134
145
|
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
146
|
+
activities = []
|
147
|
+
phrases_per_user_locale_month_year.each do |year, months|
|
148
|
+
months.each do |month, user_ids|
|
149
|
+
user_ids.each do |user_id, locales|
|
150
|
+
locales.each do |locale, days|
|
151
|
+
if opts[:daily]
|
152
|
+
days.each do |day, phrase_ids_word_counts_by_type|
|
153
|
+
activities << {:year => year, :month => month, :day => day, :locale => locale, :user_id => user_id, :activity => construct_activity_new(phrase_ids_word_counts_by_type)}
|
154
|
+
end
|
155
|
+
else
|
156
|
+
monthly_phrase_ids_word_counts_by_type = { false => {}, true => {} }
|
157
|
+
days.each do |_, phrase_ids_word_counts_by_type|
|
158
|
+
[false, true].each do |is_verification|
|
159
|
+
monthly_phrase_ids_word_counts_by_type[is_verification].merge!(phrase_ids_word_counts_by_type[is_verification])
|
160
|
+
end
|
161
|
+
end
|
162
|
+
activity = construct_activity_new(monthly_phrase_ids_word_counts_by_type)
|
163
|
+
activities << {:year => year, :month => month, :locale => locale, :user_id => user_id, :activity => activity}
|
164
|
+
end
|
165
|
+
end
|
166
|
+
end
|
167
|
+
end
|
168
|
+
end
|
169
|
+
activities.sort do |a,b|
|
170
|
+
if a[:year] == b[:year]
|
171
|
+
if a[:month] == b[:month]
|
172
|
+
if a[:user_id] == b[:user_id]
|
173
|
+
if a[:locale] == b[:locale]
|
174
|
+
if !a[:day] || !b[:day] || a[:day] == b[:day]
|
175
|
+
0
|
176
|
+
else
|
177
|
+
a[:day] <=> b[:day]
|
178
|
+
end
|
179
|
+
else
|
180
|
+
a[:locale] <=> b[:locale]
|
181
|
+
end
|
182
|
+
else
|
183
|
+
a[:user_id] <=> b[:user_id]
|
184
|
+
end
|
185
|
+
else
|
186
|
+
a[:month] <=> b[:month]
|
187
|
+
end
|
188
|
+
else
|
189
|
+
a[:year] <=> b[:year]
|
190
|
+
end
|
191
|
+
end
|
139
192
|
end
|
140
193
|
|
141
|
-
|
142
|
-
|
143
|
-
|
194
|
+
# Helper method for translator_activity_data which counts words and payment
|
195
|
+
# of a set of translations and verifications, in ((is verification bool => (phrase id =>
|
196
|
+
# source word count map)).
|
197
|
+
def self.construct_activity_new(phrase_ids_word_counts_by_type)
|
198
|
+
{}.tap do |activity|
|
199
|
+
activity[:num_translations] = phrase_ids_word_counts_by_type[false].size
|
200
|
+
activity[:word_count_translations] = phrase_ids_word_counts_by_type[false].values.sum
|
144
201
|
|
145
|
-
|
146
|
-
|
147
|
-
def self.fetch_unpaid_nonadmin_translator_list
|
148
|
-
rich_memory = RichMemory.find_by_memory_type(RichMemory::TYPE_TRANSLATOR_LIST)
|
149
|
-
if rich_memory
|
150
|
-
rich_memory.payload
|
151
|
-
else
|
152
|
-
[]
|
202
|
+
activity[:num_verifications] = phrase_ids_word_counts_by_type[true].size
|
203
|
+
activity[:word_count_verifications] = phrase_ids_word_counts_by_type[true].values.sum
|
153
204
|
end
|
154
205
|
end
|
155
206
|
|
156
|
-
#
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
else
|
162
|
-
set += [id]
|
163
|
-
end
|
164
|
-
write_unpaid_nonadmin_translator_list(set.to_a)
|
207
|
+
# Break text into words for purposes of word count.
|
208
|
+
# First uses to_s to convert text to a string; so for a nil input, returns
|
209
|
+
# empty array.
|
210
|
+
def self.segment(text)
|
211
|
+
text.to_s.scan(/\w+/)
|
165
212
|
end
|
166
213
|
|
167
|
-
|
168
|
-
|
169
|
-
def self.write_unpaid_nonadmin_translator_list translator_list
|
170
|
-
rich_memory = RichMemory.find_by_memory_type(RichMemory::TYPE_TRANSLATOR_LIST) || RichMemory.new(:memory_type => RichMemory::TYPE_TRANSLATOR_LIST)
|
171
|
-
rich_memory.payload = translator_list
|
172
|
-
rich_memory.save!
|
214
|
+
def self.activity_for_user_id uid, opts
|
215
|
+
translator_activity_data uid, opts
|
173
216
|
end
|
174
217
|
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
218
|
+
# Helper method for translator_activity_data which counts words and payment
|
219
|
+
# of a set of translations and verifications, in (is verification bool) => (phrase id =>
|
220
|
+
# English phrase value map).
|
221
|
+
#
|
222
|
+
# TODO(jkb) remove in September 2012.
|
223
|
+
def self.construct_activity(phrase_ids_values_by_type)
|
224
|
+
{}.tap do |activity|
|
225
|
+
activity[:num_translations], activity[:word_count_translations] = translation_word_count(phrase_ids_values_by_type[false])
|
226
|
+
activity[:num_verifications], activity[:word_count_verifications] = translation_word_count(phrase_ids_values_by_type[true])
|
179
227
|
end
|
180
228
|
end
|
181
229
|
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
if stats[:year] == today_last_month.year && stats[:month] == today_last_month.month && stats[:user_id] > 0
|
189
|
-
user = User.find_by_id(stats[:user_id])
|
190
|
-
stats[:smart_name] = user.try(:smart_name)
|
191
|
-
|
192
|
-
stats[:is_admin] = user.try(:role_admin?)
|
193
|
-
stats[:is_paid] = !stats[:is_admin] && !fetch_unpaid_nonadmin_translator_list.include?(stats[:user_id])
|
194
|
-
stats[:usd_amount] = Currency.convert(stats[:native_amount], stats[:currency], "USD").round
|
195
|
-
if stats[:is_paid] && stats[:usd_amount] > 0
|
196
|
-
stats[:description] = "Translator payout "
|
197
|
-
stats[:description] << "for #{Date::MONTHNAMES[today_last_month.month]} #{today_last_month.year}. "
|
198
|
-
stats[:description] << "(#{stats[:activity][:word_count_translations]} translated words and #{stats[:activity][:word_count_verifications]} verified words in #{stats[:activity][:num_translations]} translated and #{stats[:activity][:num_verifications]} verified #{stats[:locale]} phrases.)"
|
199
|
-
|
200
|
-
stats[:line_item] = LineItem.new(
|
201
|
-
:user_id => stats[:user_id],
|
202
|
-
:ready_for_release_at => Date.today,
|
203
|
-
:sub_type => LineItem::SUB_TYPE_TRANSLATION_PAYOUT,
|
204
|
-
:native_currency => stats[:currency],
|
205
|
-
:amount => stats[:usd_amount],
|
206
|
-
:item => nil,
|
207
|
-
:description => (stats[:description] + " Thank you!!")
|
208
|
-
)
|
209
|
-
end
|
210
|
-
ret << stats
|
211
|
-
end
|
212
|
-
end
|
213
|
-
ret
|
230
|
+
# Helper method for construct_activity which counts words in a set of
|
231
|
+
# translations or verifications, in phrase id => English phrase value map.
|
232
|
+
#
|
233
|
+
# TODO(jkb) remove in September 2012.
|
234
|
+
def self.translation_word_count(phrase_ids_to_values)
|
235
|
+
[phrase_ids_to_values.count, segment(phrase_ids_to_values.values.join(' ')).size]
|
214
236
|
end
|
215
237
|
|
216
238
|
# Returns all translations for a locale. filter_opts are passed on to
|
@@ -236,7 +258,7 @@ module Air18n
|
|
236
258
|
if filter_opts[:exclude_ugc] && I18n.phrase_key_is_ugc?(key)
|
237
259
|
return false
|
238
260
|
end
|
239
|
-
if filter_opts[:exclude_unused] && !I18n.
|
261
|
+
if filter_opts[:exclude_unused] && !I18n.still_used?(key)
|
240
262
|
return false
|
241
263
|
end
|
242
264
|
if filter_opts[:exclude_all]
|
data/lib/air18n/priority.rb
CHANGED
data/lib/air18n/version.rb
CHANGED
@@ -28,6 +28,8 @@ class Air18nMigration < ActiveRecord::Migration
|
|
28
28
|
t.boolean "is_verification", :default => false
|
29
29
|
t.boolean "is_stale", :default => false
|
30
30
|
t.boolean "is_latest", :default => false
|
31
|
+
t.integer "source_word_count", :default => 0
|
32
|
+
t.string "source_hash"
|
31
33
|
end
|
32
34
|
|
33
35
|
add_index "phrase_translations", ["locale", "is_latest", "is_stale", "is_verification"], :name => "index_phrase_translations_on_locale_and_flags"
|
data/make_gem
CHANGED
@@ -40,6 +40,11 @@ describe Air18n do
|
|
40
40
|
end
|
41
41
|
|
42
42
|
context 'Keeping track of priority' do
|
43
|
+
it 'should assume all phrases are used by default' do
|
44
|
+
I18n.still_used_keys_hash.should == {}
|
45
|
+
I18n.still_used?('magical new key').should == true
|
46
|
+
end
|
47
|
+
|
43
48
|
it 'should call key_used' do
|
44
49
|
Air18n::MockPriority.mock_priority({}) do |mock_priority|
|
45
50
|
I18n.t('priority, key 1', :default => 'value 1')
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
1
3
|
require 'spec_helper'
|
2
4
|
|
3
5
|
describe Air18n::PhraseTranslation do
|
@@ -34,4 +36,13 @@ describe Air18n::PhraseTranslation do
|
|
34
36
|
end
|
35
37
|
end
|
36
38
|
|
39
|
+
context 'helpers' do
|
40
|
+
it 'should be able to segment' do
|
41
|
+
Air18n::PhraseTranslation.segment('Good check-in dates.').should == ['Good', 'check', 'in', 'dates']
|
42
|
+
|
43
|
+
# Ugh.
|
44
|
+
Air18n::PhraseTranslation.segment('こんにちは、ジェーソン。お元気ですか?').should == []
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
37
48
|
end
|
data/spec/spec_helper.rb
CHANGED
@@ -60,3 +60,21 @@ def clean_database!
|
|
60
60
|
end
|
61
61
|
|
62
62
|
clean_database!
|
63
|
+
|
64
|
+
# If you want to use created_at or updated_at in a test, pass your code as a
|
65
|
+
# block to this method and the class that you model that you want to be able to
|
66
|
+
# manually timestamp as an argument.
|
67
|
+
#
|
68
|
+
# Idea and implementation from:
|
69
|
+
# http://codetunes.com/2009/05/09/turning-off-auto-timestamping-for-testing-in-rails
|
70
|
+
def without_timestamping_of(*klasses)
|
71
|
+
if block_given?
|
72
|
+
klasses.delete_if { |klass| !klass.record_timestamps }
|
73
|
+
klasses.each { |klass| klass.record_timestamps = false }
|
74
|
+
begin
|
75
|
+
yield
|
76
|
+
ensure
|
77
|
+
klasses.each { |klass| klass.record_timestamps = true }
|
78
|
+
end
|
79
|
+
end
|
80
|
+
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.7
|
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-
|
16
|
+
date: 2012-08-30 00:00:00.000000000 Z
|
17
17
|
dependencies:
|
18
18
|
- !ruby/object:Gem::Dependency
|
19
19
|
name: i18n
|