air18n 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (39) hide show
  1. data/.gitignore +18 -0
  2. data/.rvmrc +48 -0
  3. data/Gemfile +4 -0
  4. data/LICENSE +22 -0
  5. data/README.md +29 -0
  6. data/Rakefile +9 -0
  7. data/air18n.gemspec +25 -0
  8. data/lib/air18n.rb +83 -0
  9. data/lib/air18n/backend.rb +194 -0
  10. data/lib/air18n/class_methods.rb +292 -0
  11. data/lib/air18n/less_silly_chain.rb +54 -0
  12. data/lib/air18n/logging_helper.rb +13 -0
  13. data/lib/air18n/mock_priority.rb +25 -0
  14. data/lib/air18n/phrase.rb +92 -0
  15. data/lib/air18n/phrase_screenshot.rb +76 -0
  16. data/lib/air18n/phrase_translation.rb +348 -0
  17. data/lib/air18n/prim_and_proper.rb +94 -0
  18. data/lib/air18n/priority.rb +13 -0
  19. data/lib/air18n/pseudo_locales.rb +53 -0
  20. data/lib/air18n/reflection.rb +10 -0
  21. data/lib/air18n/screenshot.rb +45 -0
  22. data/lib/air18n/testing_support/factories.rb +3 -0
  23. data/lib/air18n/testing_support/factories/phrase.rb +8 -0
  24. data/lib/air18n/testing_support/factories/phrase_screenshot.rb +8 -0
  25. data/lib/air18n/testing_support/factories/phrase_translation.rb +17 -0
  26. data/lib/air18n/version.rb +3 -0
  27. data/lib/air18n/xss_detector.rb +47 -0
  28. data/lib/generators/air18n/migration/migration_generator.rb +39 -0
  29. data/lib/generators/air18n/migration/templates/active_record/migration.rb +63 -0
  30. data/spec/database.yml +3 -0
  31. data/spec/factories.rb +2 -0
  32. data/spec/lib/air18n/air18n_spec.rb +144 -0
  33. data/spec/lib/air18n/backend_spec.rb +173 -0
  34. data/spec/lib/air18n/phrase_translation_spec.rb +80 -0
  35. data/spec/lib/air18n/prim_and_proper_spec.rb +21 -0
  36. data/spec/lib/air18n/pseudo_locales_spec.rb +17 -0
  37. data/spec/lib/air18n/xss_detector_spec.rb +47 -0
  38. data/spec/spec_helper.rb +62 -0
  39. metadata +212 -0
@@ -0,0 +1,173 @@
1
+ require 'spec_helper'
2
+
3
+ require 'air18n/mock_priority'
4
+
5
+ describe Air18n::Backend do
6
+
7
+ before do
8
+ @scope = []
9
+ end
10
+
11
+ context 'Translation auto-update' do
12
+ before do
13
+ @backend = Air18n::Backend.new
14
+ end
15
+ it 'Should have default override DB value' do
16
+ phrase = FactoryGirl.create(:phrase, :value => 'original_value')
17
+ @backend.lookup(:en, phrase.key, @scope, :default => 'new_value').should == 'new_value'
18
+ phrase.reload.value.should == 'new_value'
19
+ end
20
+ it 'Should have default override empty DB' do
21
+ @backend.lookup(:en, 'key', @scope, :default => 'brand_new_value').should == 'brand_new_value'
22
+ @backend.lookup(:en, 'key', @scope).should == 'brand_new_value'
23
+ end
24
+ it 'Should have low-priority default override empty DB' do
25
+ @backend.lookup(:en, 'key', @scope, :default => 'low_priority_brand_new_value', :default_is_low_priority => true).should == 'low_priority_brand_new_value'
26
+ @backend.lookup(:en, 'key', @scope).should == 'low_priority_brand_new_value'
27
+ end
28
+ it 'Should have low-priority default *not* override nonempty DB value' do
29
+ phrase = FactoryGirl.create(:phrase, :value => 'original_value')
30
+ @backend.init_translations(I18n.default_locale)
31
+ @backend.lookup(I18n.default_locale, phrase.key, @scope, :default => 'new_value', :default_is_low_priority => true).should == 'new_value'
32
+ phrase.reload.value.should == 'original_value'
33
+ end
34
+ end
35
+
36
+ context 'Robustness to various input formats' do
37
+ before do
38
+ @backend = Air18n::Backend.new
39
+ end
40
+ it 'should not barf on arrays' do
41
+ @backend.lookup(I18n.default_locale, ['january', 'february'], @scope).should == nil
42
+ end
43
+ end
44
+
45
+ context 'I18n backend initialization' do
46
+ before :all do
47
+ @phrase1 = FactoryGirl.create(:phrase, :key => 'init, key 1', :value => 'value 1')
48
+ @phrase2 = FactoryGirl.create(:phrase, :key => 'init, key 2', :value => 'value 2')
49
+ FactoryGirl.create(:phrase_translation, :phrase => @phrase1, :value => 'French value 1', :locale => :fr)
50
+ FactoryGirl.create(:phrase_translation, :phrase => @phrase1, :value => 'Spanish value 1', :locale => :es)
51
+ FactoryGirl.create(:phrase_translation, :phrase => @phrase2, :value => 'Spanish value 2', :locale => :es)
52
+ end
53
+
54
+ it 'should lazily load all translations on startup' do
55
+ @backend = Air18n::Backend.new
56
+ @backend.available_locales.should be_empty
57
+ @backend.lookup(:es, @phrase1.key).should == 'Spanish value 1'
58
+ @backend.lookup(:fr, @phrase1.key).should == 'French value 1'
59
+ @backend.lookup(:es, @phrase2.key).should == 'Spanish value 2'
60
+
61
+ # This is undesirable behavior. Before any English lookup has been done,
62
+ # English translations aren't loaded.
63
+ @backend.lookup(:ko, @phrase1.key).should == nil
64
+
65
+ @backend.lookup(:en, @phrase1.key).should == 'value 1'
66
+ @backend.lookup(:ko, @phrase1.key).should == 'value 1'
67
+ @backend.lookup(:en, @phrase2.key).should == 'value 2'
68
+ @backend.available_locales.size.should == 3
69
+ end
70
+
71
+ it 'should load only still-used translations on startup' do
72
+ Air18n::MockPriority.mock_priority({ @phrase1.key => 1 }) do |mock_priority|
73
+ @backend = Air18n::Backend.new
74
+ @backend.lookup(:es, @phrase1.key, [], :default => @phrase1.value).should == 'Spanish value 1'
75
+ @backend.lookup(:es, @phrase2.key, [], :default => @phrase2.value).should == 'value 2'
76
+ end
77
+ end
78
+ end
79
+
80
+ context 'Pseudolocales' do
81
+ it 'should use pseudolocales for xx locale' do
82
+ @backend = Air18n::Backend.new
83
+ @backend.lookup(:xx, 'xx, key 1', @scope, :default => 'hey there').should == Air18n::PseudoLocales::translate(:xx, 'hey there')
84
+ end
85
+ end
86
+
87
+ context 'Language fallbacks' do
88
+ it 'should fall back to default locale' do
89
+ old_default_locale = I18n.default_locale
90
+ I18n.reset(:fr)
91
+ @backend = Air18n::Backend.new
92
+ @backend.lookup(:fr, 'fallbacks, key 1', @scope, :default => 'merci').should == 'merci'
93
+ @backend.lookup(:es, 'fallbacks, key 1', @scope, :default => 'merci').should == 'merci'
94
+ @backend.lookup(:es, 'fallbacks, key 1', @scope).should == 'merci'
95
+ @backend.lookup(:en, 'fallbacks, key 1', @scope).should == 'merci'
96
+
97
+ # Restore the old default locale.
98
+ I18n.reset(old_default_locale)
99
+ end
100
+
101
+ it 'should fallback to less specific locale' do
102
+ @phrase1 = FactoryGirl.create(:phrase, :key => 'less specific fallbacks, key 1', :value => 'value 1')
103
+ FactoryGirl.create(:phrase_translation, :phrase => @phrase1, :value => 'Spanish Spanish value 1', :locale => :es)
104
+ FactoryGirl.create(:phrase_translation, :phrase => @phrase1, :value => 'Latin American Spanish value 1', :locale => :"es-419")
105
+ @phrase2 = FactoryGirl.create(:phrase, :key => 'less specific fallbacks, key 2', :value => 'value 2')
106
+ FactoryGirl.create(:phrase_translation, :phrase => @phrase2, :value => 'Spanish Spanish value 2', :locale => :es)
107
+
108
+ @backend = Air18n::Backend.new
109
+ @backend.lookup(:es, 'less specific fallbacks, key 1', @scope).should == 'Spanish Spanish value 1'
110
+ @backend.lookup(:"es-419", 'less specific fallbacks, key 1', @scope).should == 'Latin American Spanish value 1'
111
+ @backend.lookup(:es, 'less specific fallbacks, key 2', @scope).should == 'Spanish Spanish value 2'
112
+ @backend.lookup(:"es-419", 'less specific fallbacks, key 2', @scope).should == 'Spanish Spanish value 2'
113
+ end
114
+ end
115
+
116
+ context 'Language variants' do
117
+ before :all do
118
+ I18n.reset(:en)
119
+ @backend = Air18n::Backend.new
120
+ end
121
+
122
+ it 'should produce British English' do
123
+ @backend.lookup(:en, 'british english, key 1', @scope, :default => 'canceled neighborhood')
124
+ @backend.lookup(:"en-GB", 'british english, key 1', @scope, :default => 'value 1').should == 'cancelled neighbourhood'
125
+ end
126
+ end
127
+
128
+ context 'Keeping track of which phrases have screenshots' do
129
+ before do
130
+ @backend = Air18n::Backend.new
131
+ end
132
+
133
+ it 'should take one screenshot of a new page' do
134
+ I18n.retrieve_phrases_needing_screenshot('new_controller#new_page').size.should == 0
135
+ @backend.lookup(:en, 'brand new key', @scope, :default => 'sparkle', :routes_context => 'new_controller#new_page')
136
+ I18n.retrieve_phrases_needing_screenshot('new_controller#new_page').should include({ 'brand new key' => 'sparkle' })
137
+ I18n.retrieve_phrases_needing_screenshot('new_controller#new_page').size.should == 0
138
+ @backend.lookup(:en, 'brand new key', @scope, :default => 'sparkle', :routes_context => 'new_controller#new_page')
139
+ I18n.retrieve_phrases_needing_screenshot('new_controller#new_page').size.should == 0
140
+ end
141
+ end
142
+
143
+ context 'XSS attack detection' do
144
+ before do
145
+ @backend = Air18n::Backend.new
146
+ end
147
+
148
+ it 'should return nil upon new tags' do
149
+ @backend.lookup(:en, 'xss, key 1', @scope, :default => 'Click <a href="/here">here</a>')
150
+
151
+ # Falls back to default when there is a missing tag.
152
+ @backend.store_translations(:es, { 'xss, key 1' => 'Too few tags' })
153
+ @backend.lookup(:es, 'xss, key 1', @scope, :default => 'Click <a href="/here">here</a>').should == 'Click <a href="/here">here</a>'
154
+
155
+ # Falls back to default when there is a missing tag.
156
+ @backend.store_translations(:es, { 'xss, key 1' => 'Too <a href="/here"><b>many</b></a> tags' })
157
+ @backend.lookup(:es, 'xss, key 1', @scope, :default => 'Click <a href="/here">here</a>').should == 'Click <a href="/here">here</a>'
158
+
159
+ # Translates nicely when tags match.
160
+ @backend.store_translations(:es, { 'xss, key 1' => 'Just <a href="/here">right</a> tags' })
161
+ @backend.lookup(:es, 'xss, key 1', @scope, :default => 'Click <a href="/here">here</a>').should == 'Just <a href="/here">right</a> tags'
162
+
163
+ # There is no French translation, so the default translation should be used and allowed.
164
+ @backend.lookup(:fr, 'xss, key 1', @scope, :default => 'Click <a href="/here">here</a>').should == 'Click <a href="/here">here</a>'
165
+
166
+ # With :disable_xss_check, anything goes.
167
+ @backend.store_translations(:fr, { 'xss, key 2' => '<p>translated <b>text</b></p>' })
168
+ @backend.lookup(:fr, 'xss, key 2', @scope, :default => '<p>rich text</p>').should == '<p>rich text</p>'
169
+ @backend.lookup(:fr, 'xss, key 2', @scope, :default => '<p>rich text</p>', :disable_xss_check => true).should == '<p>translated <b>text</b></p>'
170
+ end
171
+ end
172
+
173
+ end
@@ -0,0 +1,80 @@
1
+ require 'spec_helper'
2
+
3
+ describe Air18n::PhraseTranslation do
4
+
5
+ context 'latest flag' do
6
+ it "PhraseTranslation Should get unset when a new translation is made" do
7
+ original_translation = FactoryGirl.create(:phrase_translation)
8
+ original_translation.should be_latest
9
+ new_translation = FactoryGirl.create(:phrase_translation, :phrase => original_translation.phrase)
10
+ original_translation.reload.should_not be_latest
11
+ new_translation.should be_latest
12
+ end
13
+ end
14
+
15
+ context 'PhraseTranslation stale flag' do
16
+ it "Should get set when a phrase is updated" do
17
+ translation = FactoryGirl.create(:phrase_translation)
18
+ translation.should_not be_stale
19
+ translation.phrase.value = "new value"
20
+ translation.phrase.save!
21
+ translation.reload.should be_stale
22
+ end
23
+ end
24
+
25
+ describe 'compute monthly translator payments' do
26
+ before do
27
+ # TODO(jason) Stub out User.
28
+ # @user1 = FactoryGirl.create(:user)
29
+ # @user2 = FactoryGirl.create(:user)
30
+ @phrase1 = FactoryGirl.create(:phrase, :value => 'two words')
31
+ @phrase2 = FactoryGirl.create(:phrase, :value => 'three long words')
32
+ end
33
+
34
+ it "should pay for only last month's translation" do
35
+ pending "Stub out User class"
36
+ without_timestamping_of Air18n::PhraseTranslation do
37
+ translation = FactoryGirl.create(:phrase_translation, :phrase => @phrase1, :user => @user1, :created_at => 1.month.ago)
38
+ other_phrase_translation = FactoryGirl.create(:phrase_translation, :phrase => @phrase2, :user => @user1, :created_at => 1.month.ago)
39
+ other_user_translation = FactoryGirl.create(:phrase_translation, :phrase => @phrase1, :user => @user2, :created_at => 1.month.ago)
40
+ older_translation = FactoryGirl.create(:phrase_translation, :phrase => @phrase1, :user => @user1, :created_at => 2.months.ago)
41
+
42
+ data = Air18n::PhraseTranslation.translator_payment_data
43
+
44
+ data[0].should include(
45
+ {:locale => "es", :is_paid => true, :currency => "EUR", :usd_amount => 1, :smart_name => @user1.smart_name, :user_id => @user1.id, :month => 1.month.ago.month, :is_admin => false, :year => 1.month.ago.year })
46
+ data[0][:activity].should include(
47
+ { :payment => 5 * Air18n::PhraseTranslation::COST_PER_WORD_TRANSLATION, :num_translations => 2, :num_phrases_prev_translated => 1, :word_count_translations => 5 })
48
+
49
+ data[1].should include(
50
+ { :locale => "es", :is_paid => true, :currency => "EUR", :usd_amount => 0, :smart_name => @user2.smart_name, :user_id => @user2.id, :is_admin => false })
51
+ data[1][:activity].should include(
52
+ { :payment => 2 * Air18n::PhraseTranslation::COST_PER_WORD_TRANSLATION, :num_translations => 1, :num_phrases_prev_translated => 0, :word_count_translations => 2 })
53
+ end
54
+ end
55
+
56
+ it 'should pay less for verification' do
57
+ pending "Stub out User class"
58
+ without_timestamping_of Air18n::PhraseTranslation do
59
+ translation = FactoryGirl.create(:phrase_translation, :phrase => @phrase1, :user => @user1, :created_at => 1.month.ago)
60
+ other_phrase_translation = FactoryGirl.create(:phrase_translation, :phrase => @phrase2, :user => @user1, :created_at => 1.month.ago)
61
+ other_user_translation = FactoryGirl.create(:phrase_translation, :phrase => @phrase1, :user => @user2, :created_at => 1.month.ago)
62
+ phrase_verification = FactoryGirl.create(:phrase_translation, :phrase => @phrase2, :user => @user2, :is_verification => true, :created_at => 1.month.ago)
63
+
64
+
65
+ data = Air18n::PhraseTranslation.translator_payment_data
66
+
67
+ data[0].should include(
68
+ {:locale => "es", :is_paid => true, :currency => "EUR", :usd_amount => 1, :smart_name => @user1.smart_name, :user_id => @user1.id, :month => 1.month.ago.month, :is_admin => false, :year => 1.month.ago.year })
69
+ data[0][:activity].should include(
70
+ { :payment => 5 * Air18n::PhraseTranslation::COST_PER_WORD_TRANSLATION, :num_translations => 2, :word_count_translations => 5 })
71
+
72
+ data[1].should include(
73
+ { :locale => "es", :is_paid => true, :currency => "EUR", :usd_amount => 0, :smart_name => @user2.smart_name, :user_id => @user2.id, :is_admin => false })
74
+ data[1][:activity].should include(
75
+ { :payment => 2 * Air18n::PhraseTranslation::COST_PER_WORD_TRANSLATION + 3 * Air18n::PhraseTranslation::COST_PER_WORD_VERIFICATION, :num_translations => 1, :num_verifications => 1, :word_count_translations => 2, :word_count_verifications => 3 })
76
+ end
77
+ end
78
+ end
79
+
80
+ end
@@ -0,0 +1,21 @@
1
+ require 'spec_helper'
2
+
3
+ describe Air18n::PrimAndProper do
4
+ describe "AmericanToBritish" do
5
+ before do
6
+ @airpnp = Air18n::PrimAndProper.new
7
+ end
8
+ it "should handle simple replacement" do
9
+ @airpnp.guess('flavored inquiries', :en, :"en-GB").should == 'flavoured enquiries'
10
+ end
11
+ it "should handle capitals and punctuation" do
12
+ @airpnp.guess('Flavored inquiries.', :en, :"en-GB").should == 'Flavoured enquiries.'
13
+ end
14
+ it "should handle quotes" do
15
+ @airpnp.guess('Click "canceled" for canceling.', :en, :"en-GB").should == 'Click "cancelled" for cancelling.'
16
+ end
17
+ it "should handle vocab variants and article adaption" do
18
+ @airpnp.guess('"An apartment has an elevator with the couch."', :en, :"en-GB").should == '"A flat has a lift with the sofa."'
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,17 @@
1
+ require 'spec_helper'
2
+
3
+ describe Air18n::PseudoLocales do
4
+ context 'xx' do
5
+ it 'should work' do
6
+ Air18n::PseudoLocales.translate(:xx, 'here be %{num_pirates}, pirates.').should == '[xxxx xx %{num_pirates}, xxxxxxx.]'
7
+ end
8
+ end
9
+
10
+ context 'xx-long' do
11
+ it 'should work' do
12
+ Air18n::PseudoLocales.translate(:xxlong, 'here be %{num_pirates}, pirates.').should == 'here be %{num_pirates}, pirates. one two three four five six seven'
13
+ Air18n::PseudoLocales.translate(:xxlong, 'hey there').should == 'hey there one two'
14
+ Air18n::PseudoLocales.translate(:xxlong, 'h').should == 'h one'
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,47 @@
1
+ require 'spec_helper'
2
+
3
+ require 'air18n/xss_detector'
4
+
5
+ describe Air18n::XssDetector do
6
+ describe "has_dubious_escape_characters?" do
7
+ it "should detect backslashes" do
8
+ Air18n::XssDetector::has_dubious_escape_characters?("Let's escape a \\' there").should == true
9
+ Air18n::XssDetector::has_dubious_escape_characters?("All's clear").should == false
10
+ end
11
+ end
12
+
13
+ describe "safe?" do
14
+ it "should detect safe texts" do
15
+ Air18n::XssDetector::safe?("safe", "safe").should == { :safe => true }
16
+ Air18n::XssDetector::safe?("<tag>", "<tag>").should == { :safe => true }
17
+ Air18n::XssDetector::safe?("safe", { "safe" => "whatevs" }).should == { :safe => true }
18
+ end
19
+
20
+ it "should detect dubious escape characters" do
21
+ Air18n::XssDetector::safe?("Let's escape a \\' there", "safe").should == { :safe => false, :reason => "Backslashes are not allowed" }
22
+ Air18n::XssDetector::safe?("safe", "Let's escape a \\' there").should == { :safe => false, :reason => "Backslashes are not allowed" }
23
+ end
24
+
25
+ it "should detect tag mismatches" do
26
+ Air18n::XssDetector::safe?("<tag>", "safe").should == { :safe => false, :reason => "HTML tags don't match: #{['<tag>'].inspect} vs #{[].inspect}" }
27
+ Air18n::XssDetector::safe?("safe", "<tag>").should == { :safe => false, :reason => "HTML tags don't match: #{[].inspect} vs #{['<tag>'].inspect}" }
28
+ end
29
+ end
30
+
31
+ describe "extract_tags" do
32
+ def test(text, tags)
33
+ Air18n::XssDetector::extract_tags(text).should == tags
34
+ end
35
+
36
+ it "should work" do
37
+ test("oh boy!<script sup>hello!</script> oh boy!", ["<script sup>", "</script>"])
38
+ test("oh boy!<<script sup>hello!</script> oh boy!", ["<<script sup>hello!</script> oh boy!"])
39
+ test('Success! You\'ve invited %{number} friends. <a href="/referrals">Invite <b>more</b>.</a>', ['<a href="/referrals">', '<b>', '</b>', '</a>'])
40
+ end
41
+ end
42
+
43
+ describe 'Robustness to various input formats' do
44
+ it 'should not barf on arrays' do
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,62 @@
1
+ $LOAD_PATH << "." unless $LOAD_PATH.include?(".")
2
+ require 'logger'
3
+
4
+ begin
5
+ require "rubygems"
6
+ require "bundler"
7
+
8
+ if Gem::Version.new(Bundler::VERSION) <= Gem::Version.new("0.9.5")
9
+ raise RuntimeError, "Your bundler version is too old." +
10
+ "Run `gem install bundler` to upgrade."
11
+ end
12
+
13
+ # Set up load paths for all bundled gems
14
+ Bundler.setup
15
+ rescue Bundler::GemNotFound
16
+ raise RuntimeError, "Bundler couldn't find some gems." +
17
+ "Did you run \`bundle install\`?"
18
+ end
19
+
20
+ require "active_record"
21
+
22
+ # set adapter to use, default is sqlite3
23
+ # to use an alternative adapter run => rake spec DB='postgresql'
24
+ db_name = ENV['DB'] || 'sqlite3'
25
+ database_yml = File.expand_path('../database.yml', __FILE__)
26
+
27
+ if File.exists?(database_yml)
28
+ active_record_configuration = YAML.load_file(database_yml)
29
+
30
+ ActiveRecord::Base.configurations = active_record_configuration
31
+ config = ActiveRecord::Base.configurations[db_name]
32
+
33
+ ActiveRecord::Base.establish_connection(db_name)
34
+ ActiveRecord::Base.connection
35
+
36
+ ActiveRecord::Base.logger = Logger.new(File.join(File.dirname(__FILE__), "debug.log"))
37
+ ActiveRecord::Base.default_timezone = :utc
38
+
39
+ ActiveRecord::Base.silence do
40
+ ActiveRecord::Migration.verbose = false
41
+
42
+ require 'generators/air18n/migration/templates/active_record/migration'
43
+ Air18nMigration.up
44
+ end
45
+ else
46
+ raise "Please create #{database_yml} first to configure your database."
47
+ end
48
+
49
+ Bundler.require
50
+ require File.expand_path('../../lib/air18n', __FILE__)
51
+
52
+ # Why doesn't Bundler.require accomplish the loading of factory_girl?
53
+ require "factory_girl"
54
+ require 'air18n/testing_support/factories'
55
+
56
+ def clean_database!
57
+ Air18n::Reflection::MODELS.each do |model|
58
+ ActiveRecord::Base.connection.execute "DELETE FROM #{model.table_name}"
59
+ end
60
+ end
61
+
62
+ clean_database!
metadata ADDED
@@ -0,0 +1,212 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: air18n
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Jason Katz-Brown
9
+ - Nick Grandy
10
+ - Naseem Hakim
11
+ - Horace Ko
12
+ - Matt Baker
13
+ autorequire:
14
+ bindir: bin
15
+ cert_chain: []
16
+ date: 2012-05-08 00:00:00.000000000 Z
17
+ dependencies:
18
+ - !ruby/object:Gem::Dependency
19
+ name: i18n
20
+ requirement: !ruby/object:Gem::Requirement
21
+ none: false
22
+ requirements:
23
+ - - ! '>='
24
+ - !ruby/object:Gem::Version
25
+ version: 0.5.0
26
+ type: :runtime
27
+ prerelease: false
28
+ version_requirements: !ruby/object:Gem::Requirement
29
+ none: false
30
+ requirements:
31
+ - - ! '>='
32
+ - !ruby/object:Gem::Version
33
+ version: 0.5.0
34
+ - !ruby/object:Gem::Dependency
35
+ name: activerecord
36
+ requirement: !ruby/object:Gem::Requirement
37
+ none: false
38
+ requirements:
39
+ - - ~>
40
+ - !ruby/object:Gem::Version
41
+ version: '3.0'
42
+ type: :runtime
43
+ prerelease: false
44
+ version_requirements: !ruby/object:Gem::Requirement
45
+ none: false
46
+ requirements:
47
+ - - ~>
48
+ - !ruby/object:Gem::Version
49
+ version: '3.0'
50
+ - !ruby/object:Gem::Dependency
51
+ name: rspec
52
+ requirement: !ruby/object:Gem::Requirement
53
+ none: false
54
+ requirements:
55
+ - - ! '>='
56
+ - !ruby/object:Gem::Version
57
+ version: '0'
58
+ type: :development
59
+ prerelease: false
60
+ version_requirements: !ruby/object:Gem::Requirement
61
+ none: false
62
+ requirements:
63
+ - - ! '>='
64
+ - !ruby/object:Gem::Version
65
+ version: '0'
66
+ - !ruby/object:Gem::Dependency
67
+ name: sqlite3
68
+ requirement: !ruby/object:Gem::Requirement
69
+ none: false
70
+ requirements:
71
+ - - ! '>='
72
+ - !ruby/object:Gem::Version
73
+ version: '0'
74
+ type: :development
75
+ prerelease: false
76
+ version_requirements: !ruby/object:Gem::Requirement
77
+ none: false
78
+ requirements:
79
+ - - ! '>='
80
+ - !ruby/object:Gem::Version
81
+ version: '0'
82
+ - !ruby/object:Gem::Dependency
83
+ name: guard
84
+ requirement: !ruby/object:Gem::Requirement
85
+ none: false
86
+ requirements:
87
+ - - ! '>='
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ none: false
94
+ requirements:
95
+ - - ! '>='
96
+ - !ruby/object:Gem::Version
97
+ version: '0'
98
+ - !ruby/object:Gem::Dependency
99
+ name: guard-rspec
100
+ requirement: !ruby/object:Gem::Requirement
101
+ none: false
102
+ requirements:
103
+ - - ! '>='
104
+ - !ruby/object:Gem::Version
105
+ version: '0'
106
+ type: :development
107
+ prerelease: false
108
+ version_requirements: !ruby/object:Gem::Requirement
109
+ none: false
110
+ requirements:
111
+ - - ! '>='
112
+ - !ruby/object:Gem::Version
113
+ version: '0'
114
+ - !ruby/object:Gem::Dependency
115
+ name: factory_girl
116
+ requirement: !ruby/object:Gem::Requirement
117
+ none: false
118
+ requirements:
119
+ - - ~>
120
+ - !ruby/object:Gem::Version
121
+ version: '3.0'
122
+ type: :development
123
+ prerelease: false
124
+ version_requirements: !ruby/object:Gem::Requirement
125
+ none: false
126
+ requirements:
127
+ - - ~>
128
+ - !ruby/object:Gem::Version
129
+ version: '3.0'
130
+ description: Rails plugin with dyanmic I18n backend for amazing internationalization.
131
+ email:
132
+ - jason@airbnb.com
133
+ - nick@airbnb.com
134
+ - naseem@airbnb.com
135
+ - horace@airbnb.com
136
+ - matt.baker@airbnb.com
137
+ executables: []
138
+ extensions: []
139
+ extra_rdoc_files: []
140
+ files:
141
+ - .gitignore
142
+ - .rvmrc
143
+ - Gemfile
144
+ - LICENSE
145
+ - README.md
146
+ - Rakefile
147
+ - air18n.gemspec
148
+ - lib/air18n.rb
149
+ - lib/air18n/backend.rb
150
+ - lib/air18n/class_methods.rb
151
+ - lib/air18n/less_silly_chain.rb
152
+ - lib/air18n/logging_helper.rb
153
+ - lib/air18n/mock_priority.rb
154
+ - lib/air18n/phrase.rb
155
+ - lib/air18n/phrase_screenshot.rb
156
+ - lib/air18n/phrase_translation.rb
157
+ - lib/air18n/prim_and_proper.rb
158
+ - lib/air18n/priority.rb
159
+ - lib/air18n/pseudo_locales.rb
160
+ - lib/air18n/reflection.rb
161
+ - lib/air18n/screenshot.rb
162
+ - lib/air18n/testing_support/factories.rb
163
+ - lib/air18n/testing_support/factories/phrase.rb
164
+ - lib/air18n/testing_support/factories/phrase_screenshot.rb
165
+ - lib/air18n/testing_support/factories/phrase_translation.rb
166
+ - lib/air18n/version.rb
167
+ - lib/air18n/xss_detector.rb
168
+ - lib/generators/air18n/migration/migration_generator.rb
169
+ - lib/generators/air18n/migration/templates/active_record/migration.rb
170
+ - spec/database.yml
171
+ - spec/factories.rb
172
+ - spec/lib/air18n/air18n_spec.rb
173
+ - spec/lib/air18n/backend_spec.rb
174
+ - spec/lib/air18n/phrase_translation_spec.rb
175
+ - spec/lib/air18n/prim_and_proper_spec.rb
176
+ - spec/lib/air18n/pseudo_locales_spec.rb
177
+ - spec/lib/air18n/xss_detector_spec.rb
178
+ - spec/spec_helper.rb
179
+ homepage: http://www.github.com/airbnb/air18n
180
+ licenses: []
181
+ post_install_message:
182
+ rdoc_options: []
183
+ require_paths:
184
+ - lib
185
+ required_ruby_version: !ruby/object:Gem::Requirement
186
+ none: false
187
+ requirements:
188
+ - - ! '>='
189
+ - !ruby/object:Gem::Version
190
+ version: '0'
191
+ required_rubygems_version: !ruby/object:Gem::Requirement
192
+ none: false
193
+ requirements:
194
+ - - ! '>='
195
+ - !ruby/object:Gem::Version
196
+ version: '0'
197
+ requirements: []
198
+ rubyforge_project:
199
+ rubygems_version: 1.8.22
200
+ signing_key:
201
+ specification_version: 3
202
+ summary: Dynamic I18n backend
203
+ test_files:
204
+ - spec/database.yml
205
+ - spec/factories.rb
206
+ - spec/lib/air18n/air18n_spec.rb
207
+ - spec/lib/air18n/backend_spec.rb
208
+ - spec/lib/air18n/phrase_translation_spec.rb
209
+ - spec/lib/air18n/prim_and_proper_spec.rb
210
+ - spec/lib/air18n/pseudo_locales_spec.rb
211
+ - spec/lib/air18n/xss_detector_spec.rb
212
+ - spec/spec_helper.rb