air18n 0.1.6 → 0.1.7

Sign up to get free protection for your applications and to get access to all the features.
@@ -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