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.
@@ -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)
@@ -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) || still_used_keys_hash.include?(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
- # Helper method for translator_activity_data which counts words and payment
126
- # of a set of translations and verifications, in (is verification bool) => (phrase id =>
127
- # English phrase value map).
128
- def self.construct_activity(phrase_ids_values_by_type)
129
- {}.tap do |activity|
130
- activity[:num_translations], activity[:word_count_translations] = translation_word_count(phrase_ids_values_by_type[false])
131
- activity[:num_verifications], activity[:word_count_verifications] = translation_word_count(phrase_ids_values_by_type[true])
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
- # Helper method for construct_activity which counts words in a set of
136
- # translations or verifications, in phrase id => English phrase value map.
137
- def self.translation_word_count(phrase_ids_to_values)
138
- [phrase_ids_to_values.count, phrase_ids_to_values.values.join(' ').scan(/\w+/).size]
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
- def self.activity_for_user_id uid, opts
142
- translator_activity_data uid, opts
143
- end
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
- # Returns an array of integers representing user IDs who are not admins but
146
- # who we also do not want to pay by the word for translations.
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
- # Adds or removes a user from the unpaid-non-admin translator list.
157
- def self.set_nonadmin_translator_payment_status id, should_be_paid
158
- set = fetch_unpaid_nonadmin_translator_list.to_set
159
- if should_be_paid
160
- set -= [id]
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
- # translator_list: array of integers representing user IDs.
168
- # Throws an exception if saving fails.
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
- def self.fill_in_payment_data(activity_data, compute_payment_method)
176
- activity_data.each do |stats|
177
- stats[:currency] = 'EUR'
178
- stats[:native_amount] = compute_payment_method.call(stats[:activity][:word_count_translations], stats[:activity][:word_count_verifications], stats[:user_id], stats[:locale].to_sym)
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
- def self.translator_payment_data(compute_payment_method)
183
- today_last_month = 1.month.ago
184
- ret = []
185
- activity_data = PhraseTranslation.translator_activity_data(0, :since => 2.months.ago)
186
- fill_in_payment_data(activity_data, compute_payment_method)
187
- activity_data.each do |stats|
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.still_used_keys_hash.include?(key)
261
+ if filter_opts[:exclude_unused] && !I18n.still_used?(key)
240
262
  return false
241
263
  end
242
264
  if filter_opts[:exclude_all]
@@ -6,6 +6,9 @@ module Air18n
6
6
 
7
7
  # Returns a hash mapping translation keys to number of times they were
8
8
  # recently used.
9
+ #
10
+ # An empty hash means that we don't have data and should assume that all
11
+ # keys are still used.
9
12
  def key_usage
10
13
  {}
11
14
  end
@@ -1,3 +1,3 @@
1
1
  module Air18n
2
- VERSION = "0.1.6"
2
+ VERSION = "0.1.7"
3
3
  end
@@ -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
@@ -1,5 +1,5 @@
1
1
  # Run this to push the new version of the gem to rubygems!
2
- v=0.1.6
2
+ v=0.1.7
3
3
  gem build air18n.gemspec
4
4
  gem push air18n-${v}.gem
5
5
 
@@ -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
@@ -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.6
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-07-26 00:00:00.000000000 Z
16
+ date: 2012-08-30 00:00:00.000000000 Z
17
17
  dependencies:
18
18
  - !ruby/object:Gem::Dependency
19
19
  name: i18n