translator 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,89 @@
1
+ # Stub a Blog Posts controller
2
+ class BlogPostsController < ActionController::Base
3
+
4
+ # Sets up view paths so tests will work
5
+ before_filter :fix_view_paths
6
+
7
+ # Simulate auth filter
8
+ before_filter :authorize, :only => [:admin]
9
+
10
+ layout "blog_layout", :only => :show_with_layout
11
+
12
+ def index
13
+ # Pull out sample strings for index to the fake blog
14
+ @page_title = t('title')
15
+ @intro = translate(:intro, :owner => "Ricky Rails")
16
+ render :nothing => true, :layout => false
17
+ end
18
+
19
+ def show
20
+ # Sample blog post
21
+ render :template => "blog_posts/show"
22
+ end
23
+
24
+ def about
25
+ # About page
26
+ render :template => "blog_posts/about"
27
+ end
28
+
29
+ # Render the show action with a layout
30
+ def show_with_layout
31
+ render :template => "blog_posts/show"
32
+ end
33
+
34
+ # The archives action references a view helper
35
+ def archives
36
+ render :template => "blog_posts/archives"
37
+ end
38
+
39
+ # View that has a key that doesn't reference a valid string
40
+ def missing_translation
41
+ render :template => "blog_posts/missing_translation"
42
+ end
43
+
44
+ def different_formats
45
+ # Get the same tagline using the different formats
46
+ @taglines = []
47
+ @taglines << t('global.sub.key') # dot-sep keys
48
+ @taglines << t('sub.key', :scope => :global) # dot-sep keys with scope
49
+ @taglines << t('key', :scope => 'global.sub') # string key with dot-sep scope
50
+ @taglines << t(:key, :scope => 'global.sub') # symbol key with dot-sep score
51
+ @taglines << t(:key, :scope => %w(global sub))
52
+ render :nothing => true
53
+ end
54
+
55
+ # Partial template, but stored within this controller
56
+ def footer_partial
57
+ render :partial => "footer"
58
+ end
59
+
60
+ # Partial that is shared across controllers
61
+ def header_partial
62
+ render :partial => "shared/header"
63
+ end
64
+
65
+ def admin
66
+ # Simulate an admin page that has a protection scheme
67
+ end
68
+
69
+ def default_value
70
+ # Get a default value if the string isn't there
71
+ @title = t('not_there', :default => 'the default')
72
+ render :nothing => true
73
+ end
74
+
75
+ protected
76
+
77
+ # Simulate an auth system that prevents login
78
+ def authorize
79
+ # set a flash with a common message
80
+ flash[:error] = t('flash.invalid_login')
81
+ redirect_to :action => :index
82
+ end
83
+
84
+ def fix_view_paths
85
+ # Append the view path to get the correct views/partials
86
+ self.append_view_path("#{File.dirname(__FILE__)}/../views")
87
+ end
88
+
89
+ end
@@ -0,0 +1,10 @@
1
+ # Helper
2
+ module BlogPostsHelper
3
+
4
+ # Get the list of archives
5
+ def get_archives
6
+ # Will be scoped to controller.action ("blog_posts.archives")
7
+ t('title')
8
+ end
9
+
10
+ end
@@ -0,0 +1,10 @@
1
+ # A mailer for new comments on the fake blog
2
+ class BlogCommentMailer < ActionMailer::Base
3
+ # Send email about new comments
4
+ def comment_notification
5
+ @subject = t('subject')
6
+ end
7
+ end
8
+
9
+ # Set the path to where the mail template will be found
10
+ BlogCommentMailer.template_root = "#{File.dirname(__FILE__)}/../views"
@@ -0,0 +1,14 @@
1
+ # Model of a blog post, defined in schema.rb
2
+ class BlogPost < ActiveRecord::Base
3
+
4
+ # text for a permalink
5
+ def self.permalink(url)
6
+ t('permalink', :url => url)
7
+ end
8
+
9
+ # Has a title, author and body
10
+ def written_by
11
+ # Get sting like "Written by Ricky"
12
+ t('byline', :author => self.author)
13
+ end
14
+ end
@@ -0,0 +1,5 @@
1
+ Subject: <%= @subject %>
2
+
3
+ From,
4
+
5
+ <%= t('signoff' )%>
@@ -0,0 +1,3 @@
1
+ <hr />
2
+
3
+ <p><%= t('copyright') %></p>
@@ -0,0 +1,6 @@
1
+ <!-- Fake About page -->
2
+ <%# Bio exists in English & Spanish %>
3
+ <%= t('bio') %>
4
+
5
+ <%# Subscribe only exists in English %>
6
+ <%= t('subscribe_feed') %>
@@ -0,0 +1,2 @@
1
+ <%# Call to BlogPostsHelper %>
2
+ <%= get_archives() %>
@@ -0,0 +1,2 @@
1
+ <!-- Key should not be found -->
2
+ <p><%= t('missing_string') %></p>
@@ -0,0 +1,4 @@
1
+ <!-- Simulate blog posting page -->
2
+ <h1><%= t('title') %></h1>
3
+
4
+ <p><%= t('body', :name => 'hobbes') %></h1>
@@ -0,0 +1,9 @@
1
+ <html>
2
+
3
+ <body>
4
+ <h1><%= t('blog_title') %></h1>
5
+
6
+ <%= yield %>
7
+
8
+ </body>
9
+ </html>
@@ -0,0 +1,2 @@
1
+ <!-- The header for my awesome blog -->
2
+ <h1><%= translate('blog_name') %></h1>
@@ -0,0 +1,11 @@
1
+ ActiveRecord::Schema.define do
2
+
3
+ create_table "blog_posts", :force => true do |t|
4
+ t.column "title",:string
5
+ t.column "body", :text
6
+ t.column "author", :string
7
+ t.column "created_at", :datetime
8
+ t.column "updated_at", :datetime
9
+
10
+ end
11
+ end
@@ -0,0 +1,56 @@
1
+ en:
2
+ # Global strings
3
+ global:
4
+ sub:
5
+ key: "Hello i18n World"
6
+
7
+ # Controller
8
+ blog_posts:
9
+ # shared strings
10
+ bio: "Hello!"
11
+ subscribe_feed: "Subscribe to my feed!"
12
+
13
+ # typical actions
14
+ index:
15
+ title: "My Blog Posts"
16
+ intro: "Welcome to the blog of {{owner}}"
17
+ # specific post
18
+ show:
19
+ title: "Catz Are Cute"
20
+ body: "My cat {{name}} is the most awesome"
21
+ category: "catz, lolz"
22
+ # archives action - key used from a view helper
23
+ archives:
24
+ title: "My Blog Archives"
25
+ # footer partial (non-shared)
26
+ footer:
27
+ copyright: "Copyright 2009"
28
+
29
+ # Flash messages not specific to one action, but within a single controller
30
+ flash:
31
+ invalid_login: "Invalid login"
32
+
33
+ # shared partials in the "shared" dir
34
+ shared:
35
+ header:
36
+ blog_name: "Ricky Rocks Rails"
37
+
38
+ # Layouts
39
+ layouts:
40
+ blog_layout:
41
+ blog_title: "The Blog of Ricky"
42
+
43
+ #
44
+ # ActiveRecord models (note singular)
45
+ #
46
+ blog_post:
47
+ byline: "Written by {{author}}"
48
+ permalink: "Permalink to {{url}}"
49
+
50
+ #
51
+ # ActionMailers
52
+ #
53
+ blog_comment_mailer:
54
+ comment_notification:
55
+ subject: "New Comment Notification"
56
+ signoff: "Your Faithful Emailing Bot"
@@ -0,0 +1,16 @@
1
+ # Spanish version (pardon the bad translations)
2
+ es:
3
+ # Global strings
4
+ global:
5
+ sub:
6
+ key: "Hola i18n Mundo"
7
+
8
+ # Controller
9
+ blog_posts:
10
+ # shared strings
11
+ bio: "Hola!"
12
+
13
+ # typical actions
14
+ index:
15
+ # Purposely has the intro but *not* "title" key
16
+ intro: "Bienvenidos a el blog de {{owner}}"
@@ -0,0 +1,90 @@
1
+ # Load Rails from the app, which allows picking up a frozen rails install
2
+ # instead of from the gems
3
+ #
4
+ # Borrowed from setup in classic_pagination plugin
5
+ plugin_root = File.join(File.dirname(__FILE__), '..')
6
+ # is the plugin installed in an application?
7
+ app_root = plugin_root + '/../../..'
8
+
9
+ if File.directory? app_root + '/config'
10
+ Object.const_set(:RAILS_ENV, ENV["RAILS_ENV"] ||= "test") unless defined?(RAILS_ENV)
11
+ Object.const_set(:RAILS_ROOT, app_root) unless defined?(RAILS_ROOT)
12
+ require "#{RAILS_ROOT}/config/environment"
13
+ end
14
+
15
+ require 'pp'
16
+ require 'test/unit'
17
+ require 'rubygems'
18
+ require 'active_support'
19
+ require 'active_support/test_case'
20
+
21
+ require 'action_controller'
22
+ require 'action_controller/test_process'
23
+ require 'action_mailer'
24
+ require 'active_record'
25
+ require 'active_record/fixtures'
26
+
27
+ require 'action_pack'
28
+ require 'action_view'
29
+ require 'action_view/helpers'
30
+
31
+ # Load the Translator init after loading Rails
32
+ require File.dirname(__FILE__) + '/../init'
33
+
34
+ # Set up an ActiveRecord connection to sqlite db for testing
35
+ # Define the connector
36
+ class ActiveRecordTestConnector
37
+ cattr_accessor :able_to_connect
38
+ cattr_accessor :connected
39
+
40
+ # Set our defaults
41
+ self.connected = false
42
+ self.able_to_connect = true
43
+
44
+ class << self
45
+ def setup
46
+ unless self.connected || !self.able_to_connect
47
+ setup_connection
48
+ load_schema
49
+ self.connected = true
50
+ end
51
+ rescue Exception => e # errors from ActiveRecord setup
52
+ $stderr.puts "\nSkipping ActiveRecord assertion tests: #{e}"
53
+ self.able_to_connect = false
54
+ end
55
+
56
+ private
57
+
58
+ def setup_connection
59
+ if Object.const_defined?(:ActiveRecord)
60
+ defaults = { :database => ':memory:' }
61
+ begin
62
+ options = defaults.merge :adapter => 'sqlite3', :timeout => 500
63
+ ActiveRecord::Base.establish_connection(options)
64
+ ActiveRecord::Base.configurations = { 'sqlite3_ar_integration' => options }
65
+ ActiveRecord::Base.connection
66
+ rescue Exception # errors from establishing a connection
67
+ $stderr.puts 'SQLite 3 unavailable; trying SQLite 2.'
68
+ options = defaults.merge :adapter => 'sqlite'
69
+ ActiveRecord::Base.establish_connection(options)
70
+ ActiveRecord::Base.configurations = { 'sqlite2_ar_integration' => options }
71
+ ActiveRecord::Base.connection
72
+ end
73
+
74
+ Object.send(:const_set, :QUOTED_TYPE, ActiveRecord::Base.connection.quote_column_name('type')) unless Object.const_defined?(:QUOTED_TYPE)
75
+ else
76
+ raise "Can't setup connection since ActiveRecord isn't loaded."
77
+ end
78
+ end
79
+
80
+ # Loads the schema.rb
81
+ def load_schema
82
+ # Silence the output of creating the db
83
+ silence_stream(STDOUT) do
84
+ Dir.glob(File.dirname(__FILE__) + "/fixtures/schema.rb").each {|f| require f}
85
+ end
86
+ end
87
+ end
88
+ end
89
+
90
+ ActiveRecordTestConnector.setup
@@ -0,0 +1,353 @@
1
+ require 'test_helper'
2
+
3
+ # Include the models/helpers directories on the load path.
4
+ [:models, :helpers, :controllers].each do |path|
5
+ $:.unshift "#{File.dirname(__FILE__)}/fixtures/app/#{path}"
6
+ end
7
+
8
+ # sample AR model
9
+ require 'blog_post'
10
+ # sample ActionMailer
11
+ require 'blog_comment_mailer'
12
+ # sample controller
13
+ require 'blog_posts_controller'
14
+
15
+ # Set up simple routing for testing
16
+ ActionController::Routing::Routes.reload rescue nil
17
+ ActionController::Routing::Routes.draw do |map|
18
+ map.connect ':controller/:action/:id'
19
+ end
20
+
21
+ # For Rails 2.2 compat
22
+ parent_module = ActiveSupport::TestCase
23
+
24
+ if Rails::VERSION::MAJOR == 2 && Rails::VERSION::MINOR > 2
25
+ # Rails 2.3 compat
26
+ parent_module = ActionController::TestCase
27
+ end
28
+
29
+ # Test Translator functionality
30
+ class TranslatorTest < parent_module
31
+
32
+ def setup
33
+ # Create test locale bundle
34
+ I18n.backend = I18n::Backend::Simple.new
35
+
36
+ # tell the I18n library where to find your translations
37
+ I18n.load_path += Dir.glob(File.join(File.dirname(__FILE__), 'locales', '*.{yml,rb}'))
38
+
39
+ # reset the locale
40
+ I18n.default_locale = :en
41
+ I18n.locale = :en
42
+
43
+ # Set up test env
44
+ @controller = BlogPostsController.new
45
+ @request = ActionController::TestRequest.new
46
+ @response = ActionController::TestResponse.new
47
+ super
48
+ end
49
+
50
+ ### ActionController Tests
51
+
52
+ # Test that translate gets typical controller scoping
53
+ def test_controller_simple
54
+ get :index
55
+ assert_response :success
56
+ assert_not_nil assigns
57
+ # Test that controller could translate
58
+ assert_equal I18n.t('blog_posts.index.title'), assigns(:page_title)
59
+ assert_equal I18n.translate('blog_posts.index.intro', :owner => "Ricky Rails"), assigns(:intro)
60
+ end
61
+
62
+ # Test that if something that breaks convention is still processed correctly
63
+ # This case breaks with standard key hierarchy convention
64
+ def test_controller_different_formats
65
+ get :different_formats
66
+ assert_response :success
67
+ assert_not_nil assigns(:taglines)
68
+
69
+ expected = "Hello i18n World" # copied from en.yml
70
+
71
+ assigns(:taglines).each do |str|
72
+ assert_equal expected, str
73
+ end
74
+ end
75
+
76
+ # Test call to translate with default value
77
+ def test_controller_with_defaults
78
+ get :default_value
79
+ assert_response :success
80
+ assert_not_nil assigns(:title)
81
+
82
+ # TODO: Need better way to check that the default was only returned as last resort.
83
+ assert_equal 'the default', assigns(:title)
84
+ end
85
+
86
+ # TODO: Test bulk lookup
87
+ def test_bulk_lookup
88
+ # flunk
89
+ end
90
+
91
+ # Translator should raise an exception on a leading dot key to
92
+ # preserve Rails 2.3 behavior. It is caught & handled
93
+ def test_leading_dot_key
94
+ assert_raise Translator::TranslatorError do
95
+ Translator.translate_with_scope(["blog_posts", "show"], ".category")
96
+ end
97
+ end
98
+
99
+ # Test that first the most specific scope will be tried (controller.action) then
100
+ # back off to just the outer scope (controller)
101
+ def test_controller_shared_messages
102
+ get :admin
103
+ assert_response :redirect
104
+
105
+ # Test that t should have tried the outer scope
106
+ assert_equal I18n.t('blog_posts.flash.invalid_login'), flash[:error]
107
+ end
108
+
109
+ ### ActionView Tests
110
+
111
+ # Test that translate works in Views.
112
+ # Also tests that a dotted key (".foo") can be accepted used, since
113
+ # Rails 2.3 supports it
114
+ def test_view_show
115
+ get :show
116
+ assert_response :success
117
+ post_title = I18n.translate('blog_posts.show.title')
118
+ post_body = I18n.t('blog_posts.show.body', :name => 'hobbes') # matches show.erb
119
+
120
+ assert_match /#{post_title}/, @response.body
121
+ assert_match /#{post_body}/, @response.body
122
+ end
123
+
124
+ # Test that layouts can pull strings
125
+ def test_show_with_layout
126
+ get :show_with_layout
127
+ assert_response :success
128
+
129
+ blog_title = I18n.t('layouts.blog_layout.blog_title')
130
+ assert_match /#{blog_title}/, @response.body
131
+ end
132
+
133
+ # Test that partials pull strings from their own key
134
+ def test_view_partial
135
+ get :footer_partial
136
+ assert_response :success
137
+
138
+ footer = I18n.t('blog_posts.footer.copyright')
139
+ assert_match /#{footer}/, @response.body
140
+ end
141
+
142
+ def test_header_partial
143
+ get :header_partial
144
+ assert_response :success
145
+
146
+ blog_name = I18n.t('shared.header.blog_name')
147
+ assert_match /#{blog_name}/, @response.body
148
+ end
149
+
150
+ # Test that view helpers inherit correct scoping
151
+ def test_view_helpers
152
+ get :archives
153
+ assert_response :success
154
+
155
+ archives_title = I18n.t('blog_posts.archives.title')
156
+ assert_match /#{archives_title}/, @response.body
157
+ end
158
+
159
+ # Test that original behavior of TranslationHelper is not undone.
160
+ # It adds a <span class="translation_missing"> that should still be there
161
+ def test_missing_translation_show_in_span
162
+ Translator.strict_mode(false)
163
+
164
+ get :missing_translation
165
+ assert_response :success
166
+
167
+ # behavior added by TranslationHelper
168
+ assert_match /span class="translation_missing"/, @response.body, "Should be a span tag translation_missing"
169
+ end
170
+
171
+ # Test that strict mode prevents TranslationHelper from adding span.
172
+ def test_strict_mode_in_views
173
+ Translator.strict_mode(true)
174
+
175
+ get :missing_translation
176
+ assert_response :error
177
+ assert_match /18n::MissingTranslationData/, @response.body, "Exception should be for a missing translation"
178
+ end
179
+
180
+ ### ActionMailer Tests
181
+
182
+ def test_mailer
183
+ mail = BlogCommentMailer.create_comment_notification
184
+ # Subject is fetched from the mailer action
185
+ subject = I18n.t('blog_comment_mailer.comment_notification.subject')
186
+
187
+ # Signoff is fetched in the mail template (via addition to ActionView)
188
+ signoff = I18n.t('blog_comment_mailer.comment_notification.signoff')
189
+
190
+ assert_match /#{subject}/, mail.body
191
+ assert_match /#{signoff}/, mail.body
192
+ end
193
+
194
+ ### ActiveRecord tests
195
+
196
+ # Test that a model's method can call translate
197
+ def test_model_calling_translate
198
+ post = nil
199
+ author = "Ricky"
200
+ assert_nothing_raised do
201
+ post = BlogPost.create(:title => "First Post!", :body => "Starting my new blog about RoR", :author => author)
202
+ end
203
+ assert_not_nil post
204
+
205
+ assert_equal I18n.t('blog_post.byline', :author => author), post.written_by
206
+ end
207
+
208
+ # Test that the translate method is added as a class method too so that it can
209
+ # be used in validate calls, etc.
210
+ def test_class_method_translate
211
+
212
+ url = "http://ricky.blog"
213
+ # Call a static method
214
+ assert_equal I18n.t('blog_post.permalink', :url => url), BlogPost.permalink(url)
215
+ end
216
+
217
+ ### TestUnit helpers
218
+
219
+ def test_strict_mode
220
+ Translator.strict_mode(true)
221
+
222
+ # With strict mode on, exception should be thrown
223
+ assert_raise I18n::MissingTranslationData do
224
+ str = "Exception should be raised #{I18n.t('the_missing_key')}"
225
+ end
226
+
227
+ Translator.strict_mode(false)
228
+
229
+ assert_nothing_raised do
230
+ str = "Exception should not be raised #{I18n.t('the_missing_key')}"
231
+ end
232
+ end
233
+
234
+ # Fetch a miss
235
+ def test_assert_translated
236
+ # Within the assert_translated block, any missing keys fail the test
237
+ assert_raise Test::Unit::AssertionFailedError do
238
+ assert_translated do
239
+ str = "Exception should be raised #{I18n.t('the_missing_key')}"
240
+ end
241
+ end
242
+
243
+ assert_nothing_raised do
244
+ str = "Exception should not be raised #{I18n.t('the_missing_key')}"
245
+ end
246
+ end
247
+
248
+ # Test that marker text appears in when using pseudo-translation
249
+ def test_pseudo_translate
250
+ Translator.pseudo_translate(true)
251
+
252
+ # Create a blog post that uses translate to create a byline
253
+ blog_post = BlogPost.create!(:author => "Ricky")
254
+ assert_not_nil blog_post
255
+
256
+ assert_match Translator.pseudo_prepend, blog_post.written_by, "Should start with prepend text"
257
+ assert_match Translator.pseudo_append, blog_post.written_by, "Should end with append text"
258
+ end
259
+
260
+ # Test that markers can be changed
261
+ def test_pseudo_translate_with_diff_markers
262
+ Translator.pseudo_translate(true)
263
+
264
+ start_marker = "!!"
265
+ end_marker = "%%"
266
+
267
+ # Set the new markers
268
+ Translator.pseudo_prepend = start_marker
269
+ Translator.pseudo_append = end_marker
270
+
271
+ get :footer_partial
272
+ assert_response :success
273
+
274
+ # Test that the view has the pseudo-translated strings
275
+ copyright = I18n.t('blog_posts.footer.copyright')
276
+ assert_match /#{start_marker + copyright + end_marker}/, @response.body
277
+ end
278
+
279
+ # Test that if fallback mode is enabled, the default locale is used if
280
+ # the set locale can't be found
281
+ def test_fallback
282
+ # Enable fallback mode
283
+ Translator.fallback(true)
284
+
285
+ # Set the locale to Spanish
286
+ I18n.locale = :es
287
+
288
+ # The index action fetchs 2 keys - 1 has a Spanish translation (intro), 1 does not
289
+ get :index
290
+ assert_response :success
291
+ assert_not_nil assigns
292
+
293
+ # Test that controller could translate the intro from spanish
294
+ assert_equal I18n.t('blog_posts.index.intro', :owner => "Ricky Rails"), assigns(:intro)
295
+
296
+ # Test that global strings are found correctly when they have a prefix
297
+ assert_equal I18n.t('global.sub.key', :locale => :es), @controller.t('global.sub.key')
298
+
299
+ # Should find the English version
300
+ I18n.locale = :en # reset local so call to I18n pulls correct string
301
+ assert_equal I18n.translate('blog_posts.index.title'), assigns(:page_title)
302
+
303
+ # Test that global strings are found correctly when they have a prefix
304
+ assert_equal I18n.t('global.sub.key', :locale => :en), @controller.t('global.sub.key')
305
+ end
306
+
307
+ # Test that fallback
308
+ def test_fallback_with_scoping_backoff
309
+
310
+ # Enable fallback mode
311
+ Translator.fallback(true)
312
+
313
+ # Set the locale to Spanish
314
+ I18n.locale = :es
315
+
316
+ get :about
317
+ assert_response :success
318
+
319
+ # Test that the Spanish version was found
320
+ bio = I18n.t('blog_posts.bio', :locale => :es)
321
+ assert_match /#{bio}/, @response.body
322
+
323
+ # Only English version of this string
324
+ subscribe = I18n.t('blog_posts.subscribe_feed', :locale => :en)
325
+ assert_match /#{subscribe}/, @response.body
326
+ end
327
+
328
+ # Test that we can set up a callback for missing translations
329
+ def test_missing_translation_callback
330
+ test_exception = nil
331
+ test_key = nil
332
+ test_options = nil
333
+
334
+ Translator.set_missing_translation_callback do |ex, key, options|
335
+ test_exception = ex
336
+ test_key = key
337
+ test_options = options
338
+ end
339
+
340
+ get :missing_translation
341
+ assert_response :success
342
+ assert_equal "missing_string", test_key
343
+ assert_not_nil test_options
344
+ assert_not_nil test_exception
345
+ end
346
+
347
+ # Test the generic translate method on Translator that does lookup without a scope, but includes fallback behavior.
348
+ def test_generic_translate_methods
349
+ assert_equal I18n.t('blog_posts.index.intro', :owner => "Ricky Rails"), Translator.translate('blog_posts.index.intro', :owner => "Ricky Rails")
350
+ assert_equal I18n.t('blog_posts.footer.copyright'), Translator.t('blog_posts.footer.copyright')
351
+ end
352
+
353
+ end