air18n 0.1.6 → 0.1.7
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/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
|