air18n 0.1.57 → 0.3.0
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.rb +1 -0
- data/lib/air18n/class_methods.rb +2 -1
- data/lib/air18n/metrics.rb +211 -0
- data/lib/air18n/prim_and_proper.rb +8 -0
- data/lib/air18n/version.rb +1 -1
- data/spec/lib/air18n/backend_spec.rb +5 -5
- data/spec/lib/air18n/metrics_spec.rb +276 -0
- data/spec/lib/air18n/prim_and_proper_spec.rb +13 -0
- metadata +6 -3
    
        data/lib/air18n.rb
    CHANGED
    
    
    
        data/lib/air18n/class_methods.rb
    CHANGED
    
    | @@ -216,7 +216,8 @@ module Air18n | |
| 216 216 |  | 
| 217 217 | 
             
                  # Use locale fallbacks that always include default locale as the last
         | 
| 218 218 | 
             
                  # fallback.
         | 
| 219 | 
            -
                  @fallbacks  | 
| 219 | 
            +
                  @fallbacks ||= I18n::Locale::Fallbacks.new
         | 
| 220 | 
            +
                  @fallbacks.defaults = [default_locale]
         | 
| 220 221 |  | 
| 221 222 | 
             
                  # Make the single instance of Air18n::Backend.
         | 
| 222 223 | 
             
                  @air18n_backend = Air18n::Backend.new
         | 
| @@ -0,0 +1,211 @@ | |
| 1 | 
            +
            module Air18n
         | 
| 2 | 
            +
              module Metrics
         | 
| 3 | 
            +
                TARGET_PRIORITY_PHRASE_TRANSLATION_PERCENT = 98
         | 
| 4 | 
            +
             | 
| 5 | 
            +
                def self.translation_progress(page_ids)
         | 
| 6 | 
            +
                  search_scope = Air18n::Phrase.still_used
         | 
| 7 | 
            +
                  search_scope = search_scope.select(
         | 
| 8 | 
            +
                    '`phrases`.`id`, `phrases`.`key`, ' +
         | 
| 9 | 
            +
                    '`phrase_translations`.`locale` AS latest_translation_locale, ' +
         | 
| 10 | 
            +
                    '`phrase_translations`.`user_id` AS latest_translation_user_id, ' +
         | 
| 11 | 
            +
                    '`phrase_translations`.`created_at` AS latest_translation_created_at, ' +
         | 
| 12 | 
            +
                    '`phrase_translations`.`is_stale` AS latest_translation_is_stale, ' +
         | 
| 13 | 
            +
                    '`phrase_translations`.`is_verification` AS latest_translation_is_verification'
         | 
| 14 | 
            +
                  )
         | 
| 15 | 
            +
                  search_scope = search_scope.pull_in_all_up_to_date_phrase_translations
         | 
| 16 | 
            +
                  search_scope = search_scope.group('`phrases`.`id`, `phrase_translations`.`phrase_id`, `latest_translation_locale`')
         | 
| 17 | 
            +
             | 
| 18 | 
            +
                  if page_ids.present?
         | 
| 19 | 
            +
                    search_scope = search_scope.joins(:phrase_screenshots)
         | 
| 20 | 
            +
                    conditions = page_ids.map do |page_id|
         | 
| 21 | 
            +
                      controller_action = Air18n::PhraseScreenshot.deserialize_page_id(page_id)
         | 
| 22 | 
            +
                      "(phrase_screenshots.controller = '#{controller_action.controller}' AND phrase_screenshots.action = '#{controller_action.action}')"
         | 
| 23 | 
            +
                    end
         | 
| 24 | 
            +
                    search_scope = search_scope.where(conditions.join(' OR '))
         | 
| 25 | 
            +
                  end
         | 
| 26 | 
            +
             | 
| 27 | 
            +
                  progress = {}
         | 
| 28 | 
            +
             | 
| 29 | 
            +
                  phrases_and_translations = search_scope.all
         | 
| 30 | 
            +
             | 
| 31 | 
            +
                  # This block is useful for debugging.
         | 
| 32 | 
            +
                  if false
         | 
| 33 | 
            +
                    LoggingHelper.info "Phrases and translations:"
         | 
| 34 | 
            +
                    phrases_and_translations.each do |thing|
         | 
| 35 | 
            +
                      LoggingHelper.info "key #{thing.key} locale #{thing.latest_translation_locale} user #{thing.latest_translation_user_id} at #{thing.latest_translation_created_at} stale? #{thing.latest_translation_is_stale} verified? #{thing.latest_translation_is_verification}"
         | 
| 36 | 
            +
                    end
         | 
| 37 | 
            +
                  end
         | 
| 38 | 
            +
             | 
| 39 | 
            +
                  priority_keys = phrases_and_translations.map { |phrase_and_translation| phrase_and_translation.key }.uniq
         | 
| 40 | 
            +
                  number_of_priority_phrases = priority_keys.size
         | 
| 41 | 
            +
                  number_of_priority_words = priority_keys.map { |k| word_counts[k] }.sum
         | 
| 42 | 
            +
                  locale_to_translations = phrases_and_translations.group_by { |phrase_and_translation| phrase_and_translation.latest_translation_locale }
         | 
| 43 | 
            +
                  locale_to_translations.each do |locale, phrases_and_translations_for_locale|
         | 
| 44 | 
            +
                    if locale.present?
         | 
| 45 | 
            +
                      locale = locale.to_sym
         | 
| 46 | 
            +
                      progress[locale] ||= {}
         | 
| 47 | 
            +
                      phrases_with_translations_for_locale = phrases_and_translations_for_locale.find_all { |phrase_and_translation| phrase_and_translation.latest_translation_user_id }
         | 
| 48 | 
            +
                      phrases_with_verifications_for_locale = phrases_with_translations_for_locale.find_all { |phrase_and_translation| true_value?(phrase_and_translation.latest_translation_is_verification) }
         | 
| 49 | 
            +
             | 
| 50 | 
            +
                      words_with_translations_for_locale = phrases_with_translations_for_locale.map { |p| word_counts[p.key] }.sum
         | 
| 51 | 
            +
                      words_with_verifications_for_locale = phrases_with_verifications_for_locale.map { |p| word_counts[p.key] }.sum
         | 
| 52 | 
            +
                      progress[locale][:phrases] = number_of_priority_phrases
         | 
| 53 | 
            +
                      progress[locale][:words] = number_of_priority_words
         | 
| 54 | 
            +
             | 
| 55 | 
            +
                      progress[locale][:phrases_translated] = phrases_with_translations_for_locale.size
         | 
| 56 | 
            +
                      progress[locale][:words_translated] = words_with_translations_for_locale
         | 
| 57 | 
            +
             | 
| 58 | 
            +
                      progress[locale][:phrases_verified] = phrases_with_verifications_for_locale.size
         | 
| 59 | 
            +
                      progress[locale][:words_verified] = words_with_verifications_for_locale
         | 
| 60 | 
            +
             | 
| 61 | 
            +
                      phrases_translated_in_last_day = phrases_with_translations_for_locale.find_all { |phrase_and_translation| since?(phrase_and_translation.latest_translation_created_at, 1.day.ago) }
         | 
| 62 | 
            +
                      phrases_translated_in_last_week = phrases_with_translations_for_locale.find_all { |phrase_and_translation| since?(phrase_and_translation.latest_translation_created_at, 1.week.ago) }
         | 
| 63 | 
            +
                      progress[locale][:phrases_translated_in_last_day] = phrases_translated_in_last_day.size
         | 
| 64 | 
            +
                      progress[locale][:words_translated_in_last_day] = phrases_translated_in_last_day.map { |p| word_counts[p.key] }.sum
         | 
| 65 | 
            +
                      progress[locale][:phrases_translated_in_last_week] = phrases_translated_in_last_week.size
         | 
| 66 | 
            +
                      progress[locale][:words_translated_in_last_week] = phrases_translated_in_last_week.map { |p| word_counts[p.key] }.sum
         | 
| 67 | 
            +
             | 
| 68 | 
            +
                      progress[locale][:phrases_translated_percent] = (100 * progress[locale][:phrases_translated].to_f / progress[locale][:phrases]).round
         | 
| 69 | 
            +
                      progress[locale][:words_translated_percent] = (100 * progress[locale][:words_translated].to_f / progress[locale][:words]).round
         | 
| 70 | 
            +
                      progress[locale][:phrases_verified_percent] = (100 * progress[locale][:phrases_verified].to_f / progress[locale][:phrases]).round
         | 
| 71 | 
            +
                      progress[locale][:words_verified_percent] = (100 * progress[locale][:words_verified].to_f / progress[locale][:words]).round
         | 
| 72 | 
            +
                      if progress[locale][:phrases_translated_percent] >= TARGET_PRIORITY_PHRASE_TRANSLATION_PERCENT
         | 
| 73 | 
            +
                        progress[locale][:phrases_progress] = :good
         | 
| 74 | 
            +
                      elsif progress[locale][:phrases_translated_in_last_day] < 50
         | 
| 75 | 
            +
                        progress[locale][:phrases_progress] = :bad
         | 
| 76 | 
            +
                      else
         | 
| 77 | 
            +
                        progress[locale][:phrases_progress] = :okay
         | 
| 78 | 
            +
                      end
         | 
| 79 | 
            +
             | 
| 80 | 
            +
                      progress[locale][:phrases_remaining_to_translate] = progress[locale][:phrases] - progress[locale][:phrases_translated]
         | 
| 81 | 
            +
                      progress[locale][:words_remaining_to_translate] = progress[locale][:words] - progress[locale][:words_translated]
         | 
| 82 | 
            +
             | 
| 83 | 
            +
                      progress[locale][:phrases_remaining_to_verify] = progress[locale][:phrases] - progress[locale][:phrases_verified]
         | 
| 84 | 
            +
                      progress[locale][:words_remaining_to_verify] = progress[locale][:words] - progress[locale][:words_verified]
         | 
| 85 | 
            +
             | 
| 86 | 
            +
                      progress[locale][:translators] = phrases_with_translations_for_locale.inject(Set.new) do |carry, value|
         | 
| 87 | 
            +
                        if since?(value.latest_translation_created_at, 28.days.ago)
         | 
| 88 | 
            +
                          carry.add(value.latest_translation_user_id)
         | 
| 89 | 
            +
                        end
         | 
| 90 | 
            +
                        carry
         | 
| 91 | 
            +
                      end
         | 
| 92 | 
            +
                    end
         | 
| 93 | 
            +
                  end
         | 
| 94 | 
            +
             | 
| 95 | 
            +
                  progress
         | 
| 96 | 
            +
                end
         | 
| 97 | 
            +
             | 
| 98 | 
            +
                def self.translator_progress(locale, user_id)
         | 
| 99 | 
            +
                  scope = PhraseTranslation.where(:locale => locale).where(:user_id => user_id)
         | 
| 100 | 
            +
                  scope = scope.where('created_at >= ?', 28.days.ago)
         | 
| 101 | 
            +
                  scope = scope.group(:phrase_id, :source_hash)
         | 
| 102 | 
            +
                  scope.select("`id`, `key`, `source_word_count`")
         | 
| 103 | 
            +
             | 
| 104 | 
            +
                  words_translated_last_month = 0
         | 
| 105 | 
            +
                  words_translated_last_week = 0
         | 
| 106 | 
            +
                  words_verified_last_month = 0
         | 
| 107 | 
            +
                  words_verified_last_week = 0
         | 
| 108 | 
            +
                  phrases_translated_last_month = 0
         | 
| 109 | 
            +
                  number_of_later_modified_phrases_last_month = 0
         | 
| 110 | 
            +
                  phrases_translated = {}
         | 
| 111 | 
            +
                  scope.find_each do |pt|
         | 
| 112 | 
            +
                    if pt.created_at >= 1.week.ago
         | 
| 113 | 
            +
                      if pt.is_verification?
         | 
| 114 | 
            +
                        words_verified_last_week += pt.source_word_count
         | 
| 115 | 
            +
                      else
         | 
| 116 | 
            +
                        words_translated_last_week += pt.source_word_count
         | 
| 117 | 
            +
                      end
         | 
| 118 | 
            +
                    end
         | 
| 119 | 
            +
             | 
| 120 | 
            +
                    if pt.is_verification?
         | 
| 121 | 
            +
                      words_verified_last_month += pt.source_word_count
         | 
| 122 | 
            +
                    else
         | 
| 123 | 
            +
                      words_translated_last_month += pt.source_word_count
         | 
| 124 | 
            +
                    end
         | 
| 125 | 
            +
             | 
| 126 | 
            +
                    if !pt.is_verification?
         | 
| 127 | 
            +
                      later_modifications = PhraseTranslation.
         | 
| 128 | 
            +
                        where(:phrase_id => pt.phrase_id, :source_hash => pt.source_hash).
         | 
| 129 | 
            +
                        where('created_at > ?', pt.created_at).
         | 
| 130 | 
            +
                        where('user_id != ?', pt.user_id).
         | 
| 131 | 
            +
                        where('payment_details NOT LIKE ?', '{"v3":{"t":0,%').
         | 
| 132 | 
            +
                        count
         | 
| 133 | 
            +
                      if later_modifications > 0
         | 
| 134 | 
            +
                        number_of_later_modified_phrases_last_month += 1
         | 
| 135 | 
            +
                      end
         | 
| 136 | 
            +
                      phrases_translated_last_month += 1
         | 
| 137 | 
            +
                    end
         | 
| 138 | 
            +
                  end
         | 
| 139 | 
            +
             | 
| 140 | 
            +
                  daily_average = (words_translated_last_month + words_verified_last_month) / 28
         | 
| 141 | 
            +
                  later_modified_percent = if phrases_translated_last_month == 0
         | 
| 142 | 
            +
                                             0
         | 
| 143 | 
            +
                                           else
         | 
| 144 | 
            +
                                             (100.0 * number_of_later_modified_phrases_last_month.to_f / phrases_translated_last_month).round
         | 
| 145 | 
            +
                                           end
         | 
| 146 | 
            +
                  classification = words_translated_last_month > words_verified_last_month ? :translator : :verifier
         | 
| 147 | 
            +
             | 
| 148 | 
            +
                  {
         | 
| 149 | 
            +
                    :user_id => user_id,
         | 
| 150 | 
            +
                    :classification => classification,
         | 
| 151 | 
            +
                    :phrases_translated_last_month => phrases_translated_last_month,
         | 
| 152 | 
            +
                    :words_translated_last_month => words_translated_last_month,
         | 
| 153 | 
            +
                    :words_translated_last_week => words_translated_last_week,
         | 
| 154 | 
            +
                    :words_verified_last_month => words_verified_last_month,
         | 
| 155 | 
            +
                    :words_verified_last_week => words_verified_last_week,
         | 
| 156 | 
            +
                    :words_verified_last_week => words_verified_last_week,
         | 
| 157 | 
            +
                    :daily_average => daily_average,
         | 
| 158 | 
            +
                    :number_of_later_modified_phrases_last_month => number_of_later_modified_phrases_last_month,
         | 
| 159 | 
            +
                    :later_modified_percent => later_modified_percent
         | 
| 160 | 
            +
                  }
         | 
| 161 | 
            +
                end
         | 
| 162 | 
            +
             | 
| 163 | 
            +
                # Computes translation progress for all pages, then translator progress for
         | 
| 164 | 
            +
                # all translators in each locale.
         | 
| 165 | 
            +
                def self.metrics_suite
         | 
| 166 | 
            +
                  p = translation_progress(nil)
         | 
| 167 | 
            +
                  p.each do |locale, info|
         | 
| 168 | 
            +
                    info[:translator_progress] = {}
         | 
| 169 | 
            +
                    info[:translators].each do |user_id|
         | 
| 170 | 
            +
                      info[:translator_progress][user_id] = translator_progress(locale, user_id)
         | 
| 171 | 
            +
                    end
         | 
| 172 | 
            +
                  end
         | 
| 173 | 
            +
                end
         | 
| 174 | 
            +
             | 
| 175 | 
            +
                # Returns scope for searching for most recent PhraseTranslations of a
         | 
| 176 | 
            +
                # translator.
         | 
| 177 | 
            +
                def self.recent_work(locale, user_id)
         | 
| 178 | 
            +
                  scope = Air18n::PhraseTranslation
         | 
| 179 | 
            +
                  scope = scope.order('`phrase_translations`.`created_at` DESC')
         | 
| 180 | 
            +
                  scope.includes(:phrase_revision)
         | 
| 181 | 
            +
                  if user_id.present?
         | 
| 182 | 
            +
                    scope = scope.where(:user_id => user_id)
         | 
| 183 | 
            +
                  end
         | 
| 184 | 
            +
                  if locale.present?
         | 
| 185 | 
            +
                    scope = scope.where(:locale => locale)
         | 
| 186 | 
            +
                  end
         | 
| 187 | 
            +
                  scope
         | 
| 188 | 
            +
                end
         | 
| 189 | 
            +
             | 
| 190 | 
            +
                private
         | 
| 191 | 
            +
             | 
| 192 | 
            +
                def self.word_counts
         | 
| 193 | 
            +
                  # Set up a map of phrase key -> word count.
         | 
| 194 | 
            +
                  @word_counts ||=
         | 
| 195 | 
            +
                    {}.tap do |ret|
         | 
| 196 | 
            +
                      Air18n::Phrase.still_used.find_each do |p|
         | 
| 197 | 
            +
                        ret[p.key] = PhraseTranslation.segment(p.value).size
         | 
| 198 | 
            +
                      end
         | 
| 199 | 
            +
                    end
         | 
| 200 | 
            +
                end
         | 
| 201 | 
            +
             | 
| 202 | 
            +
                def self.since?(created_at, time_ago)
         | 
| 203 | 
            +
                  time_obj = created_at.is_a?(String) ? Time.parse(created_at) : created_at
         | 
| 204 | 
            +
                  time_obj >= time_ago
         | 
| 205 | 
            +
                end
         | 
| 206 | 
            +
             | 
| 207 | 
            +
                def self.true_value?(tf_value)
         | 
| 208 | 
            +
                  tf_value == 1 || tf_value == "t" || tf_value == "1"
         | 
| 209 | 
            +
                end
         | 
| 210 | 
            +
              end
         | 
| 211 | 
            +
            end
         | 
| @@ -12,6 +12,7 @@ module Air18n | |
| 12 12 | 
             
                                           :"en-CA" => AmericanToCanadian.new,
         | 
| 13 13 | 
             
                                           :"en-GB" => AmericanToBritish.new,
         | 
| 14 14 | 
             
                                           :"en-NZ" => AmericanToKiwi.new,
         | 
| 15 | 
            +
                                           :"en-SG" => AmericanToSinglish.new,
         | 
| 15 16 | 
             
                                         },
         | 
| 16 17 | 
             
                                  :de => { :"de-CH" => GermanToSwissGerman.new }
         | 
| 17 18 | 
             
                                }
         | 
| @@ -127,6 +128,13 @@ module Air18n | |
| 127 128 | 
             
                end
         | 
| 128 129 | 
             
              end
         | 
| 129 130 |  | 
| 131 | 
            +
              # Singlish is just normal Colonial English for now.
         | 
| 132 | 
            +
              class AmericanToSinglish < AmericanToColonial
         | 
| 133 | 
            +
                def initialize
         | 
| 134 | 
            +
                  super
         | 
| 135 | 
            +
                end
         | 
| 136 | 
            +
              end
         | 
| 137 | 
            +
             | 
| 130 138 | 
             
              # Only changes
         | 
| 131 139 | 
             
              #   "[flav]or" to "[flav]our"
         | 
| 132 140 | 
             
              #   "[travel]ed" to "[travel]led"
         | 
    
        data/lib/air18n/version.rb
    CHANGED
    
    
| @@ -158,12 +158,12 @@ describe Air18n::Backend do | |
| 158 158 | 
             
              context 'Language fallbacks' do
         | 
| 159 159 | 
             
                it 'should fall back to default locale' do
         | 
| 160 160 | 
             
                  old_default_locale = I18n.default_locale
         | 
| 161 | 
            -
                  I18n.reset(: | 
| 161 | 
            +
                  I18n.reset(:zq)
         | 
| 162 162 | 
             
                  @backend = Air18n::Backend.new
         | 
| 163 | 
            -
                  @backend.lookup(: | 
| 164 | 
            -
                  @backend.lookup(: | 
| 165 | 
            -
                  @backend.lookup(: | 
| 166 | 
            -
                  @backend.lookup(: | 
| 163 | 
            +
                  @backend.lookup(:zq, 'fallbacks, key 1', @scope, :default => 'merci').should == 'merci'
         | 
| 164 | 
            +
                  @backend.lookup(:zv, 'fallbacks, key 1', @scope, :default => 'merci').should == 'merci'
         | 
| 165 | 
            +
                  @backend.lookup(:zv, 'fallbacks, key 1', @scope).should == 'merci'
         | 
| 166 | 
            +
                  @backend.lookup(:zy, 'fallbacks, key 1', @scope).should == 'merci'
         | 
| 167 167 |  | 
| 168 168 | 
             
                  # Restore the old default locale.
         | 
| 169 169 | 
             
                  I18n.reset(old_default_locale)
         | 
| @@ -0,0 +1,276 @@ | |
| 1 | 
            +
            require 'spec_helper'
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            describe Air18n::Metrics do
         | 
| 4 | 
            +
              before :all do
         | 
| 5 | 
            +
                @translator1_fr_id = 7
         | 
| 6 | 
            +
                @translator2_fr_id = 9
         | 
| 7 | 
            +
             | 
| 8 | 
            +
                # Translator 3 for French hasn't translated for 2 months. Shouldn't be
         | 
| 9 | 
            +
                # included in recent translators list.
         | 
| 10 | 
            +
                @translator3_fr_id = 11
         | 
| 11 | 
            +
             | 
| 12 | 
            +
                # Translator 4 fixes translator 1 and 2's work.
         | 
| 13 | 
            +
                @translator4_fr_id = 17
         | 
| 14 | 
            +
             | 
| 15 | 
            +
                @translator1_it_id = 13
         | 
| 16 | 
            +
                @translator2_it_id = 19
         | 
| 17 | 
            +
             | 
| 18 | 
            +
                @translator1_ko_id = 15
         | 
| 19 | 
            +
             | 
| 20 | 
            +
                @phrase1 = FactoryGirl.create(:phrase, :value => 'one two three four five six seven eight nine')
         | 
| 21 | 
            +
                @phrase2 = FactoryGirl.create(:phrase)
         | 
| 22 | 
            +
                @phrase3 = FactoryGirl.create(:phrase)
         | 
| 23 | 
            +
                @phrase4 = FactoryGirl.create(:phrase)
         | 
| 24 | 
            +
                @phrase5 = FactoryGirl.create(:phrase)
         | 
| 25 | 
            +
                @phrase6 = FactoryGirl.create(:phrase)
         | 
| 26 | 
            +
                @phrase7 = FactoryGirl.create(:phrase)
         | 
| 27 | 
            +
             | 
| 28 | 
            +
                # info#press is a manually-prioritized page.
         | 
| 29 | 
            +
                @phrase3_screenshot1 = FactoryGirl.create(
         | 
| 30 | 
            +
                  :phrase_screenshot,
         | 
| 31 | 
            +
                  :controller => 'info',
         | 
| 32 | 
            +
                  :action => 'press',
         | 
| 33 | 
            +
                  :phrase => @phrase3)
         | 
| 34 | 
            +
             | 
| 35 | 
            +
                # info#unimportant is unimportant.
         | 
| 36 | 
            +
                @phrase2_screenshot1 = FactoryGirl.create(
         | 
| 37 | 
            +
                  :phrase_screenshot,
         | 
| 38 | 
            +
                  :controller => 'info',
         | 
| 39 | 
            +
                  :action => 'unimportant',
         | 
| 40 | 
            +
                  :phrase => @phrase2)
         | 
| 41 | 
            +
             | 
| 42 | 
            +
                # info#obsolete_page is a obsolete page with only obsolete phrases.
         | 
| 43 | 
            +
                @phrase4_screenshot1 = FactoryGirl.create(
         | 
| 44 | 
            +
                  :phrase_screenshot,
         | 
| 45 | 
            +
                  :controller => 'info',
         | 
| 46 | 
            +
                  :action => 'obsolete_page',
         | 
| 47 | 
            +
                  :phrase => @phrase4)
         | 
| 48 | 
            +
             | 
| 49 | 
            +
                # home#index is high-pageviews high priority.
         | 
| 50 | 
            +
                @phrase1_screenshot1 = FactoryGirl.create(
         | 
| 51 | 
            +
                  :phrase_screenshot,
         | 
| 52 | 
            +
                  :controller => 'home',
         | 
| 53 | 
            +
                  :action => 'index',
         | 
| 54 | 
            +
                  :phrase => @phrase1)
         | 
| 55 | 
            +
                @phrase3_screenshot2 = FactoryGirl.create(
         | 
| 56 | 
            +
                  :phrase_screenshot,
         | 
| 57 | 
            +
                  :controller => 'home',
         | 
| 58 | 
            +
                  :action => 'index',
         | 
| 59 | 
            +
                  :phrase => @phrase3)
         | 
| 60 | 
            +
                @phrase5_screenshot1 = FactoryGirl.create(
         | 
| 61 | 
            +
                  :phrase_screenshot,
         | 
| 62 | 
            +
                  :controller => 'home',
         | 
| 63 | 
            +
                  :action => 'index',
         | 
| 64 | 
            +
                  :phrase => @phrase5)
         | 
| 65 | 
            +
                @phrase6_screenshot1 = FactoryGirl.create(
         | 
| 66 | 
            +
                  :phrase_screenshot,
         | 
| 67 | 
            +
                  :controller => 'home',
         | 
| 68 | 
            +
                  :action => 'index',
         | 
| 69 | 
            +
                  :phrase => @phrase6)
         | 
| 70 | 
            +
                @phrase7_screenshot1 = FactoryGirl.create(
         | 
| 71 | 
            +
                  :phrase_screenshot,
         | 
| 72 | 
            +
                  :controller => 'home',
         | 
| 73 | 
            +
                  :action => 'index',
         | 
| 74 | 
            +
                  :phrase => @phrase7)
         | 
| 75 | 
            +
             | 
| 76 | 
            +
                without_timestamping_of Air18n::PhraseTranslation do
         | 
| 77 | 
            +
                  @translation0_of_phrase1_fr_today = FactoryGirl.create(
         | 
| 78 | 
            +
                    :phrase_translation,
         | 
| 79 | 
            +
                    :locale => 'fr',
         | 
| 80 | 
            +
                    :phrase => @phrase1,
         | 
| 81 | 
            +
                    :user_id => @translator1_fr_id,
         | 
| 82 | 
            +
                    :created_at => 3.hours.ago)
         | 
| 83 | 
            +
                  @translation1_of_phrase1_fr_today = FactoryGirl.create(
         | 
| 84 | 
            +
                    :phrase_translation,
         | 
| 85 | 
            +
                    :locale => 'fr',
         | 
| 86 | 
            +
                    :phrase => @phrase1,
         | 
| 87 | 
            +
                    :user_id => @translator1_fr_id,
         | 
| 88 | 
            +
                    :created_at => 2.hours.ago)
         | 
| 89 | 
            +
                  @verification1_of_phrase1_fr_today = FactoryGirl.create(
         | 
| 90 | 
            +
                    :phrase_translation,
         | 
| 91 | 
            +
                    :locale => 'fr',
         | 
| 92 | 
            +
                    :phrase => @phrase1,
         | 
| 93 | 
            +
                    :user_id => @translator2_fr_id,
         | 
| 94 | 
            +
                    :is_verification => true,
         | 
| 95 | 
            +
                    :created_at => 1.hour.ago)
         | 
| 96 | 
            +
                  @translation2_of_phrase1_fr_today = FactoryGirl.create(
         | 
| 97 | 
            +
                    :phrase_translation,
         | 
| 98 | 
            +
                    :locale => 'fr',
         | 
| 99 | 
            +
                    :phrase => @phrase1,
         | 
| 100 | 
            +
                    :user_id => @translator4_fr_id,
         | 
| 101 | 
            +
                    :value => "something brand new!",
         | 
| 102 | 
            +
                    :payment_details => '{"v3":{"t":9,"v":0}}',
         | 
| 103 | 
            +
                    :is_verification => true,
         | 
| 104 | 
            +
                    :created_at => 1.hours.ago)
         | 
| 105 | 
            +
                  @translation1_of_phrase2_fr_today = FactoryGirl.create(
         | 
| 106 | 
            +
                    :phrase_translation,
         | 
| 107 | 
            +
                    :locale => 'fr',
         | 
| 108 | 
            +
                    :phrase => @phrase2,
         | 
| 109 | 
            +
                    :user_id => @translator1_fr_id,
         | 
| 110 | 
            +
                    :created_at => 59.minutes.ago)
         | 
| 111 | 
            +
                  @translation1_of_phrase6_fr_way_old = FactoryGirl.create(
         | 
| 112 | 
            +
                    :phrase_translation,
         | 
| 113 | 
            +
                    :locale => 'fr',
         | 
| 114 | 
            +
                    :phrase => @phrase6,
         | 
| 115 | 
            +
                    :user_id => @translator3_fr_id,
         | 
| 116 | 
            +
                    :created_at => 2.months.ago)
         | 
| 117 | 
            +
                  @translation2_of_phrase3_fr_old = FactoryGirl.create(
         | 
| 118 | 
            +
                    :phrase_translation,
         | 
| 119 | 
            +
                    :locale => 'fr',
         | 
| 120 | 
            +
                    :phrase => @phrase3,
         | 
| 121 | 
            +
                    :user_id => @translator1_fr_id,
         | 
| 122 | 
            +
                    :created_at => 3.days.ago)
         | 
| 123 | 
            +
                  @translation2_of_phrase4_fr_old = FactoryGirl.create(
         | 
| 124 | 
            +
                    :phrase_translation,
         | 
| 125 | 
            +
                    :locale => 'fr',
         | 
| 126 | 
            +
                    :phrase => @phrase4,
         | 
| 127 | 
            +
                    :user_id => @translator1_fr_id,
         | 
| 128 | 
            +
                    :created_at => 3.days.ago)
         | 
| 129 | 
            +
             | 
| 130 | 
            +
                  # With Italian, we test the odd case where multiple translations have
         | 
| 131 | 
            +
                  # the "is_latest" flag set.
         | 
| 132 | 
            +
                  #
         | 
| 133 | 
            +
                  # (I'm not sure why this ever happens, but in prod database, it does.)
         | 
| 134 | 
            +
                  @translation1_of_phrase1_it_today = FactoryGirl.create(
         | 
| 135 | 
            +
                    :phrase_translation,
         | 
| 136 | 
            +
                    :locale => 'it',
         | 
| 137 | 
            +
                    :phrase => @phrase1,
         | 
| 138 | 
            +
                    :user_id => @translator1_it_id,
         | 
| 139 | 
            +
                    :created_at => 2.hours.ago)
         | 
| 140 | 
            +
                  @translation2_of_phrase1_it_today = FactoryGirl.create(
         | 
| 141 | 
            +
                    :phrase_translation,
         | 
| 142 | 
            +
                    :locale => 'it',
         | 
| 143 | 
            +
                    :phrase => @phrase1,
         | 
| 144 | 
            +
                    :user_id => @translator2_it_id,
         | 
| 145 | 
            +
                    :created_at => 1.hour.ago)
         | 
| 146 | 
            +
                  @translation1_of_phrase1_it_today.update_column(:is_latest, true)
         | 
| 147 | 
            +
                  @translation2_of_phrase1_it_today.update_column(:is_latest, true)
         | 
| 148 | 
            +
             | 
| 149 | 
            +
                  @translation1_of_phrase3_ko_old = FactoryGirl.create(
         | 
| 150 | 
            +
                    :phrase_translation,
         | 
| 151 | 
            +
                    :locale => 'ko',
         | 
| 152 | 
            +
                    :phrase => @phrase3,
         | 
| 153 | 
            +
                    :user_id => @translator1_ko_id,
         | 
| 154 | 
            +
                    :created_at => 3.days.ago)
         | 
| 155 | 
            +
             | 
| 156 | 
            +
                  @translation1_of_phrase5_fr_today = FactoryGirl.create(
         | 
| 157 | 
            +
                    :phrase_translation,
         | 
| 158 | 
            +
                    :locale => 'fr',
         | 
| 159 | 
            +
                    :phrase => @phrase5,
         | 
| 160 | 
            +
                    :is_stale => true,
         | 
| 161 | 
            +
                    :user_id => @translator1_fr_id,
         | 
| 162 | 
            +
                    :created_at => 58.minutes.ago)
         | 
| 163 | 
            +
             | 
| 164 | 
            +
                  # Phrase 7 is not translated at all.
         | 
| 165 | 
            +
                end
         | 
| 166 | 
            +
              end
         | 
| 167 | 
            +
             | 
| 168 | 
            +
              before :each do
         | 
| 169 | 
            +
                @still_used_phrases = [@phrase1.id, @phrase2.id, @phrase3.id, @phrase5.id, @phrase6.id, @phrase7.id]
         | 
| 170 | 
            +
                I18n.stub(:still_used_phrase_ids => @still_used_phrases)
         | 
| 171 | 
            +
              end
         | 
| 172 | 
            +
             | 
| 173 | 
            +
              context "#translation_progress" do
         | 
| 174 | 
            +
                it "should measure priority phrase progress" do
         | 
| 175 | 
            +
                  ppp = Air18n::Metrics.translation_progress(['home#index', 'home#other_action', 'info#press'])
         | 
| 176 | 
            +
                  ppp.size.should == 3
         | 
| 177 | 
            +
             | 
| 178 | 
            +
                  ppp[:fr].should include({
         | 
| 179 | 
            +
                    :phrases => 5,
         | 
| 180 | 
            +
                    :words => 21,
         | 
| 181 | 
            +
                    :phrases_translated => 3,
         | 
| 182 | 
            +
                    :words_translated => 15,
         | 
| 183 | 
            +
                    :phrases_verified => 1,
         | 
| 184 | 
            +
                    :words_verified => 9,
         | 
| 185 | 
            +
                    :phrases_translated_in_last_day => 1,
         | 
| 186 | 
            +
                    :words_translated_in_last_day => 9,
         | 
| 187 | 
            +
                    :phrases_translated_in_last_week => 2,
         | 
| 188 | 
            +
                    :words_translated_in_last_week => 12,
         | 
| 189 | 
            +
                    :phrases_translated_percent => 60,
         | 
| 190 | 
            +
                    :words_translated_percent => 71,
         | 
| 191 | 
            +
                    :phrases_verified_percent => 20,
         | 
| 192 | 
            +
                    :words_verified_percent => 43,
         | 
| 193 | 
            +
                    :phrases_progress => :bad,
         | 
| 194 | 
            +
                    :phrases_remaining_to_translate => 2,
         | 
| 195 | 
            +
                    :words_remaining_to_translate => 6,
         | 
| 196 | 
            +
                    :phrases_remaining_to_verify => 4,
         | 
| 197 | 
            +
                    :words_remaining_to_verify => 12,
         | 
| 198 | 
            +
             | 
| 199 | 
            +
                    # translator2_fr doesn't appear because he hasn't last touched any phrase.
         | 
| 200 | 
            +
                    :translators => Set.new([@translator1_fr_id, @translator4_fr_id]) })
         | 
| 201 | 
            +
             | 
| 202 | 
            +
                  ppp[:it].should include({
         | 
| 203 | 
            +
                    :phrases => 5,
         | 
| 204 | 
            +
                    :words => 21,
         | 
| 205 | 
            +
                    :phrases_translated => 1,
         | 
| 206 | 
            +
                    :words_translated => 9,
         | 
| 207 | 
            +
                    :phrases_translated_in_last_day => 1,
         | 
| 208 | 
            +
                    :phrases_translated_in_last_week => 1,
         | 
| 209 | 
            +
                    :phrases_translated_percent => 20,
         | 
| 210 | 
            +
                    :words_translated_percent => 43,
         | 
| 211 | 
            +
                    :phrases_progress => :bad,
         | 
| 212 | 
            +
                    :phrases_remaining_to_translate => 4,
         | 
| 213 | 
            +
                    :words_remaining_to_translate => 12 })
         | 
| 214 | 
            +
                  ppp[:ko].should include({
         | 
| 215 | 
            +
                    :phrases => 5,
         | 
| 216 | 
            +
                    :words => 21,
         | 
| 217 | 
            +
                    :phrases_translated => 1,
         | 
| 218 | 
            +
                    :words_translated => 3,
         | 
| 219 | 
            +
                    :phrases_translated_in_last_day => 0,
         | 
| 220 | 
            +
                    :phrases_translated_in_last_week => 1,
         | 
| 221 | 
            +
                    :phrases_translated_percent => 20,
         | 
| 222 | 
            +
                    :words_translated_percent => 14,
         | 
| 223 | 
            +
                    :phrases_progress => :bad,
         | 
| 224 | 
            +
                    :phrases_remaining_to_translate => 4,
         | 
| 225 | 
            +
                    :words_remaining_to_translate => 18,
         | 
| 226 | 
            +
                    :translators => Set.new([@translator1_ko_id]) })
         | 
| 227 | 
            +
                end
         | 
| 228 | 
            +
              end
         | 
| 229 | 
            +
             | 
| 230 | 
            +
              context "#translator_progress" do
         | 
| 231 | 
            +
                it "should measure priority phrase progress" do
         | 
| 232 | 
            +
                  p1 = Air18n::Metrics.translator_progress(:fr, @translator1_fr_id)
         | 
| 233 | 
            +
                  p1.should include({
         | 
| 234 | 
            +
                    :user_id => @translator1_fr_id,
         | 
| 235 | 
            +
                    :classification => :translator,
         | 
| 236 | 
            +
                    :words_translated_last_month => 21,
         | 
| 237 | 
            +
                    :words_translated_last_week => 21,
         | 
| 238 | 
            +
                    :words_verified_last_month => 0,
         | 
| 239 | 
            +
                    :words_verified_last_week => 0,
         | 
| 240 | 
            +
                    :daily_average => 0,
         | 
| 241 | 
            +
                    :phrases_translated_last_month => 5,
         | 
| 242 | 
            +
                    :number_of_later_modified_phrases_last_month => 1,
         | 
| 243 | 
            +
                    :later_modified_percent => 20,
         | 
| 244 | 
            +
                  })
         | 
| 245 | 
            +
             | 
| 246 | 
            +
                  p2 = Air18n::Metrics.translator_progress(:fr, @translator2_fr_id)
         | 
| 247 | 
            +
                  p2.should include({
         | 
| 248 | 
            +
                    :user_id => @translator2_fr_id,
         | 
| 249 | 
            +
                    :classification => :verifier,
         | 
| 250 | 
            +
                    :words_translated_last_month => 0,
         | 
| 251 | 
            +
                    :words_translated_last_week => 0,
         | 
| 252 | 
            +
                    :words_verified_last_month => 9,
         | 
| 253 | 
            +
                    :words_verified_last_week => 9,
         | 
| 254 | 
            +
                    :daily_average => 0,
         | 
| 255 | 
            +
                    :phrases_translated_last_month => 0,
         | 
| 256 | 
            +
                    :number_of_later_modified_phrases_last_month => 0,
         | 
| 257 | 
            +
                    :later_modified_percent => 0,
         | 
| 258 | 
            +
                  })
         | 
| 259 | 
            +
                end
         | 
| 260 | 
            +
              end
         | 
| 261 | 
            +
             | 
| 262 | 
            +
              context "#metrics_suite" do
         | 
| 263 | 
            +
                it "should work" do
         | 
| 264 | 
            +
                  s = Air18n::Metrics.metrics_suite
         | 
| 265 | 
            +
                  s[:fr][:words_translated].should == 18
         | 
| 266 | 
            +
                  s[:fr][:translator_progress].size.should == 2
         | 
| 267 | 
            +
                end
         | 
| 268 | 
            +
              end
         | 
| 269 | 
            +
             | 
| 270 | 
            +
              context "#recent_work" do
         | 
| 271 | 
            +
                it "should scope somebody's recent work" do
         | 
| 272 | 
            +
                  work = Air18n::Metrics.recent_work(:fr, @translator1_fr_id).limit(2).all
         | 
| 273 | 
            +
                  work.should == [@translation1_of_phrase5_fr_today, @translation1_of_phrase2_fr_today]
         | 
| 274 | 
            +
                end
         | 
| 275 | 
            +
              end
         | 
| 276 | 
            +
            end
         | 
| @@ -58,6 +58,19 @@ describe Air18n::PrimAndProper do | |
| 58 58 | 
             
                end
         | 
| 59 59 | 
             
              end
         | 
| 60 60 |  | 
| 61 | 
            +
              describe Air18n::AmericanToSinglish do
         | 
| 62 | 
            +
                it "have a complete travel-related vocab list with Kia ora" do
         | 
| 63 | 
            +
                  @airpnp.guess(
         | 
| 64 | 
            +
                    "traveled realize flavor center learned practices airplane inquire specialties " +
         | 
| 65 | 
            +
                    "inquiry artifact program livable among spelled judgment defense fulfill spilled " +
         | 
| 66 | 
            +
                    "vacation apartment elevator couch coupon cell Hi bathroom neighborhood's",
         | 
| 67 | 
            +
                    :en, :"en-SG").should ==
         | 
| 68 | 
            +
                    "travelled realise flavour centre learnt practises aeroplane enquire specialities " +
         | 
| 69 | 
            +
                    "enquiry artefact programme liveable amongst spelt judgement defence fulfil spilt " +
         | 
| 70 | 
            +
                    "holiday apartment lift sofa voucher mobile Hi bathroom neighbourhood's"
         | 
| 71 | 
            +
                end
         | 
| 72 | 
            +
              end
         | 
| 73 | 
            +
             | 
| 61 74 | 
             
              describe Air18n::AmericanToCanadian do
         | 
| 62 75 | 
             
                it "have a complete travel-related vocab list without ize/ise variants" do
         | 
| 63 76 | 
             
                  @airpnp.guess(
         | 
    
        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. | 
| 4 | 
            +
              version: 0.3.0
         | 
| 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: 2013- | 
| 16 | 
            +
            date: 2013-02-21 00:00:00.000000000 Z
         | 
| 17 17 | 
             
            dependencies:
         | 
| 18 18 | 
             
            - !ruby/object:Gem::Dependency
         | 
| 19 19 | 
             
              name: i18n
         | 
| @@ -168,6 +168,7 @@ files: | |
| 168 168 | 
             
            - lib/air18n/default_text_change_observer.rb
         | 
| 169 169 | 
             
            - lib/air18n/less_silly_chain.rb
         | 
| 170 170 | 
             
            - lib/air18n/logging_helper.rb
         | 
| 171 | 
            +
            - lib/air18n/metrics.rb
         | 
| 171 172 | 
             
            - lib/air18n/mock_priority.rb
         | 
| 172 173 | 
             
            - lib/air18n/phrase.rb
         | 
| 173 174 | 
             
            - lib/air18n/phrase_revision.rb
         | 
| @@ -192,6 +193,7 @@ files: | |
| 192 193 | 
             
            - spec/lib/air18n/air18n_spec.rb
         | 
| 193 194 | 
             
            - spec/lib/air18n/backend_spec.rb
         | 
| 194 195 | 
             
            - spec/lib/air18n/chunk_cache_spec.rb
         | 
| 196 | 
            +
            - spec/lib/air18n/metrics_spec.rb
         | 
| 195 197 | 
             
            - spec/lib/air18n/phrase_spec.rb
         | 
| 196 198 | 
             
            - spec/lib/air18n/phrase_translation_spec.rb
         | 
| 197 199 | 
             
            - spec/lib/air18n/prim_and_proper_spec.rb
         | 
| @@ -219,7 +221,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement | |
| 219 221 | 
             
                  version: '0'
         | 
| 220 222 | 
             
            requirements: []
         | 
| 221 223 | 
             
            rubyforge_project: 
         | 
| 222 | 
            -
            rubygems_version: 1.8. | 
| 224 | 
            +
            rubygems_version: 1.8.24
         | 
| 223 225 | 
             
            signing_key: 
         | 
| 224 226 | 
             
            specification_version: 3
         | 
| 225 227 | 
             
            summary: Dynamic I18n backend
         | 
| @@ -229,6 +231,7 @@ test_files: | |
| 229 231 | 
             
            - spec/lib/air18n/air18n_spec.rb
         | 
| 230 232 | 
             
            - spec/lib/air18n/backend_spec.rb
         | 
| 231 233 | 
             
            - spec/lib/air18n/chunk_cache_spec.rb
         | 
| 234 | 
            +
            - spec/lib/air18n/metrics_spec.rb
         | 
| 232 235 | 
             
            - spec/lib/air18n/phrase_spec.rb
         | 
| 233 236 | 
             
            - spec/lib/air18n/phrase_translation_spec.rb
         | 
| 234 237 | 
             
            - spec/lib/air18n/prim_and_proper_spec.rb
         |