integrating I18n into Rails

This commit is contained in:
Sven Fuchs 2008-06-19 16:25:27 +02:00
parent 40557e17dd
commit 45d41f0dad
17 changed files with 1082 additions and 168 deletions

View File

@ -32,6 +32,8 @@ require 'action_view/base'
require 'action_view/partials'
require 'action_view/template_error'
require 'action_view/lang/en-US.rb'
ActionView::Base.class_eval do
include ActionView::Partials

View File

@ -151,12 +151,17 @@ module ActionView
# instance yourself and set it up. View the source of this method to see how easy it is.
def error_messages_for(*params)
options = params.extract_options!.symbolize_keys
if object = options.delete(:object)
objects = [object].flatten
else
objects = params.collect {|object_name| instance_variable_get("@#{object_name}") }.compact
end
count = objects.inject(0) {|sum, object| sum + object.errors.count }
count = objects.inject(0) {|sum, object| sum + object.errors.count }
locale = options[:locale]
locale ||= request.locale if respond_to?(:request)
unless count.zero?
html = {}
[:id, :class].each do |key|
@ -168,21 +173,29 @@ module ActionView
end
end
options[:object_name] ||= params.first
options[:header_message] = "#{pluralize(count, 'error')} prohibited this #{options[:object_name].to_s.gsub('_', ' ')} from being saved" unless options.include?(:header_message)
options[:message] ||= 'There were problems with the following fields:' unless options.include?(:message)
error_messages = objects.sum {|object| object.errors.full_messages.map {|msg| content_tag(:li, msg) } }.join
contents = ''
contents << content_tag(options[:header_tag] || :h2, options[:header_message]) unless options[:header_message].blank?
contents << content_tag(:p, options[:message]) unless options[:message].blank?
contents << content_tag(:ul, error_messages)
I18n.with_options :locale => locale, :scope => [:active_record, :error] do |locale|
header_message = if options.include?(:header_message)
options[:header_message]
else
object_name = options[:object_name].to_s.gsub('_', ' ')
locale.t :header_message, :count => count, :object_name => object_name
end
message = options.include?(:message) ? options[:message] : locale.t(:message)
error_messages = objects.sum {|object| object.errors.full_messages.map {|msg| content_tag(:li, msg) } }.join
content_tag(:div, contents, html)
contents = ''
contents << content_tag(options[:header_tag] || :h2, header_message) unless header_message.blank?
contents << content_tag(:p, message) unless message.blank?
contents << content_tag(:ul, error_messages)
content_tag(:div, contents, html)
end
else
''
end
end
private
def all_input_tags(record, record_name, options)
input_block = options[:input_block] || default_input_block

View File

@ -58,35 +58,43 @@ module ActionView
# distance_of_time_in_words(to_time, from_time, true) # => over 6 years
# distance_of_time_in_words(Time.now, Time.now) # => less than a minute
#
def distance_of_time_in_words(from_time, to_time = 0, include_seconds = false)
def distance_of_time_in_words(from_time, to_time = 0, include_seconds = false, options = {})
locale = options[:locale]
locale ||= request.locale if respond_to?(:request)
from_time = from_time.to_time if from_time.respond_to?(:to_time)
to_time = to_time.to_time if to_time.respond_to?(:to_time)
distance_in_minutes = (((to_time - from_time).abs)/60).round
distance_in_seconds = ((to_time - from_time).abs).round
case distance_in_minutes
when 0..1
return (distance_in_minutes == 0) ? 'less than a minute' : '1 minute' unless include_seconds
case distance_in_seconds
when 0..4 then 'less than 5 seconds'
when 5..9 then 'less than 10 seconds'
when 10..19 then 'less than 20 seconds'
when 20..39 then 'half a minute'
when 40..59 then 'less than a minute'
else '1 minute'
end
I18n.with_options :locale => locale, :scope => :'datetime.distance_in_words' do |locale|
case distance_in_minutes
when 0..1
return distance_in_minutes == 0 ?
locale.t(:less_than_x_minutes, :count => 1) :
locale.t(:x_minutes, :count => distance_in_minutes) unless include_seconds
when 2..44 then "#{distance_in_minutes} minutes"
when 45..89 then 'about 1 hour'
when 90..1439 then "about #{(distance_in_minutes.to_f / 60.0).round} hours"
when 1440..2879 then '1 day'
when 2880..43199 then "#{(distance_in_minutes / 1440).round} days"
when 43200..86399 then 'about 1 month'
when 86400..525599 then "#{(distance_in_minutes / 43200).round} months"
when 525600..1051199 then 'about 1 year'
else "over #{(distance_in_minutes / 525600).round} years"
case distance_in_seconds
when 0..4 then locale.t :less_than_x_seconds, :count => 5
when 5..9 then locale.t :less_than_x_seconds, :count => 10
when 10..19 then locale.t :less_than_x_seconds, :count => 20
when 20..39 then locale.t :half_a_minute
when 40..59 then locale.t :less_than_x_minutes, :count => 1
else locale.t :x_minutes, :count => 1
end
when 2..44 then locale.t :x_minutes, :count => distance_in_minutes
when 45..89 then locale.t :about_x_hours, :count => 1
when 90..1439 then locale.t :about_x_hours, :count => (distance_in_minutes.to_f / 60.0).round
when 1440..2879 then locale.t :x_days, :count => 1
when 2880..43199 then locale.t :x_days, :count => (distance_in_minutes / 1440).round
when 43200..86399 then locale.t :about_x_months, :count => 1
when 86400..525599 then locale.t :x_months, :count => (distance_in_minutes / 43200).round
when 525600..1051199 then locale.t :about_x_years, :count => 1
else locale.t :over_x_years, :count => (distance_in_minutes / 525600).round
end
end
end
end
# Like distance_of_time_in_words, but where <tt>to_time</tt> is fixed to <tt>Time.now</tt>.
#
@ -498,13 +506,19 @@ module ActionView
# select_month(Date.today, :use_month_names => %w(Januar Februar Marts ...))
#
def select_month(date, options = {}, html_options = {})
locale = options[:locale]
locale ||= request.locale if respond_to?(:request)
val = date ? (date.kind_of?(Fixnum) ? date : date.month) : ''
if options[:use_hidden]
hidden_html(options[:field_name] || 'month', val, options)
else
month_options = []
month_names = options[:use_month_names] || (options[:use_short_month] ? Date::ABBR_MONTHNAMES : Date::MONTHNAMES)
month_names = options[:use_month_names] || begin
(options[:use_short_month] ? :'date.abbr_month_names' : :'date.month_names').t locale
end
month_names.unshift(nil) if month_names.size < 13
1.upto(12) do |month_number|
month_name = if options[:use_month_numbers]
month_number
@ -522,7 +536,7 @@ module ActionView
end
select_html(options[:field_name] || 'month', month_options.join, options, html_options)
end
end
end
# Returns a select tag with options for each of the five years on each side of the current, which is selected. The five year radius
# can be changed using the <tt>:start_year</tt> and <tt>:end_year</tt> keys in the +options+. Both ascending and descending year
@ -612,15 +626,17 @@ module ActionView
private
def date_or_time_select(options, html_options = {})
locale = options[:locale]
defaults = { :discard_type => true }
options = defaults.merge(options)
datetime = value(object)
datetime ||= default_time_from_options(options[:default]) unless options[:include_blank]
position = { :year => 1, :month => 2, :day => 3, :hour => 4, :minute => 5, :second => 6 }
order = (options[:order] ||= [:year, :month, :day])
order = options[:order] ||= :'date.order'.t(locale)
# Discard explicit and implicit by not being included in the :order
discard = {}
discard[:year] = true if options[:discard_year] or !order.include?(:year)
@ -629,19 +645,19 @@ module ActionView
discard[:hour] = true if options[:discard_hour]
discard[:minute] = true if options[:discard_minute] or discard[:hour]
discard[:second] = true unless options[:include_seconds] && !discard[:minute]
# If the day is hidden and the month is visible, the day should be set to the 1st so all month choices are valid
# (otherwise it could be 31 and february wouldn't be a valid date)
if datetime && discard[:day] && !discard[:month]
datetime = datetime.change(:day => 1)
end
# Maintain valid dates by including hidden fields for discarded elements
[:day, :month, :year].each { |o| order.unshift(o) unless order.include?(o) }
# Ensure proper ordering of :hour, :minute and :second
[:hour, :minute, :second].each { |o| order.delete(o); order.push(o) }
date_or_time_select = ''
order.reverse.each do |param|
# Send hidden fields for discarded elements once output has started
@ -656,9 +672,8 @@ module ActionView
when :second then options[:include_seconds] ? " : " : ""
else ""
end)
end
date_or_time_select
end

View File

@ -276,16 +276,25 @@ module ActionView
# that they will be listed above the rest of the (long) list.
#
# NOTE: Only the option tags are returned, you have to wrap this call in a regular HTML select tag.
def country_options_for_select(selected = nil, priority_countries = nil)
def country_options_for_select(*args)
options = args.extract_options!
locale = options[:locale]
locale ||= request.locale if respond_to?(:request)
selected, priority_countries = *args
countries = :'countries.names'.t options[:locale]
country_options = ""
if priority_countries
# TODO priority_countries need to be translated?
country_options += options_for_select(priority_countries, selected)
country_options += "<option value=\"\" disabled=\"disabled\">-------------</option>\n"
end
return country_options + options_for_select(COUNTRIES, selected)
return country_options + options_for_select(countries, selected)
end
# Returns a string of option tags for pretty much any time zone in the
# world. Supply a TimeZone name as +selected+ to have it marked as the
@ -340,43 +349,8 @@ module ActionView
end
# All the countries included in the country_options output.
COUNTRIES = ["Afghanistan", "Aland Islands", "Albania", "Algeria", "American Samoa", "Andorra", "Angola",
"Anguilla", "Antarctica", "Antigua And Barbuda", "Argentina", "Armenia", "Aruba", "Australia", "Austria",
"Azerbaijan", "Bahamas", "Bahrain", "Bangladesh", "Barbados", "Belarus", "Belgium", "Belize", "Benin",
"Bermuda", "Bhutan", "Bolivia", "Bosnia and Herzegowina", "Botswana", "Bouvet Island", "Brazil",
"British Indian Ocean Territory", "Brunei Darussalam", "Bulgaria", "Burkina Faso", "Burundi", "Cambodia",
"Cameroon", "Canada", "Cape Verde", "Cayman Islands", "Central African Republic", "Chad", "Chile", "China",
"Christmas Island", "Cocos (Keeling) Islands", "Colombia", "Comoros", "Congo",
"Congo, the Democratic Republic of the", "Cook Islands", "Costa Rica", "Cote d'Ivoire", "Croatia", "Cuba",
"Cyprus", "Czech Republic", "Denmark", "Djibouti", "Dominica", "Dominican Republic", "Ecuador", "Egypt",
"El Salvador", "Equatorial Guinea", "Eritrea", "Estonia", "Ethiopia", "Falkland Islands (Malvinas)",
"Faroe Islands", "Fiji", "Finland", "France", "French Guiana", "French Polynesia",
"French Southern Territories", "Gabon", "Gambia", "Georgia", "Germany", "Ghana", "Gibraltar", "Greece", "Greenland", "Grenada", "Guadeloupe", "Guam", "Guatemala", "Guernsey", "Guinea",
"Guinea-Bissau", "Guyana", "Haiti", "Heard and McDonald Islands", "Holy See (Vatican City State)",
"Honduras", "Hong Kong", "Hungary", "Iceland", "India", "Indonesia", "Iran, Islamic Republic of", "Iraq",
"Ireland", "Isle of Man", "Israel", "Italy", "Jamaica", "Japan", "Jersey", "Jordan", "Kazakhstan", "Kenya",
"Kiribati", "Korea, Democratic People's Republic of", "Korea, Republic of", "Kuwait", "Kyrgyzstan",
"Lao People's Democratic Republic", "Latvia", "Lebanon", "Lesotho", "Liberia", "Libyan Arab Jamahiriya",
"Liechtenstein", "Lithuania", "Luxembourg", "Macao", "Macedonia, The Former Yugoslav Republic Of",
"Madagascar", "Malawi", "Malaysia", "Maldives", "Mali", "Malta", "Marshall Islands", "Martinique",
"Mauritania", "Mauritius", "Mayotte", "Mexico", "Micronesia, Federated States of", "Moldova, Republic of",
"Monaco", "Mongolia", "Montenegro", "Montserrat", "Morocco", "Mozambique", "Myanmar", "Namibia", "Nauru",
"Nepal", "Netherlands", "Netherlands Antilles", "New Caledonia", "New Zealand", "Nicaragua", "Niger",
"Nigeria", "Niue", "Norfolk Island", "Northern Mariana Islands", "Norway", "Oman", "Pakistan", "Palau",
"Palestinian Territory, Occupied", "Panama", "Papua New Guinea", "Paraguay", "Peru", "Philippines",
"Pitcairn", "Poland", "Portugal", "Puerto Rico", "Qatar", "Reunion", "Romania", "Russian Federation",
"Rwanda", "Saint Barthelemy", "Saint Helena", "Saint Kitts and Nevis", "Saint Lucia",
"Saint Pierre and Miquelon", "Saint Vincent and the Grenadines", "Samoa", "San Marino",
"Sao Tome and Principe", "Saudi Arabia", "Senegal", "Serbia", "Seychelles", "Sierra Leone", "Singapore",
"Slovakia", "Slovenia", "Solomon Islands", "Somalia", "South Africa",
"South Georgia and the South Sandwich Islands", "Spain", "Sri Lanka", "Sudan", "Suriname",
"Svalbard and Jan Mayen", "Swaziland", "Sweden", "Switzerland", "Syrian Arab Republic",
"Taiwan, Province of China", "Tajikistan", "Tanzania, United Republic of", "Thailand", "Timor-Leste",
"Togo", "Tokelau", "Tonga", "Trinidad and Tobago", "Tunisia", "Turkey", "Turkmenistan",
"Turks and Caicos Islands", "Tuvalu", "Uganda", "Ukraine", "United Arab Emirates", "United Kingdom",
"United States", "United States Minor Outlying Islands", "Uruguay", "Uzbekistan", "Vanuatu", "Venezuela",
"Viet Nam", "Virgin Islands, British", "Virgin Islands, U.S.", "Wallis and Futuna", "Western Sahara",
"Yemen", "Zambia", "Zimbabwe"] unless const_defined?("COUNTRIES")
# only included for backwards compatibility, please use the I18n interface
COUNTRIES = :'countries.names'.t 'en-US' unless const_defined?("COUNTRIES")
end
class InstanceTag #:nodoc:

View File

@ -69,13 +69,19 @@ module ActionView
# number_to_currency(1234567890.50, :unit => "&pound;", :separator => ",", :delimiter => "", :format => "%n %u")
# # => 1234567890,50 &pound;
def number_to_currency(number, options = {})
options = options.stringify_keys
precision = options["precision"] || 2
unit = options["unit"] || "$"
separator = precision > 0 ? options["separator"] || "." : ""
delimiter = options["delimiter"] || ","
format = options["format"] || "%u%n"
options = options.symbolize_keys
locale = options[:locale]
locale ||= request.locale if respond_to?(:request)
defaults = :'currency.format'.t(locale) || {}
precision = options[:precision] || defaults[:precision]
unit = options[:unit] || defaults[:unit]
separator = options[:separator] || defaults[:separator]
delimiter = options[:delimiter] || defaults[:delimiter]
format = options[:format] || defaults[:format]
separator = '' if precision == 0
begin
parts = number_with_precision(number, precision).split('.')
format.gsub(/%n/, number_with_delimiter(parts[0], delimiter) + separator + parts[1].to_s).gsub(/%u/, unit)

View File

@ -0,0 +1,93 @@
I18n.backend.add_translations :'en-US', {
:date => {
:formats => {
:default => "%Y-%m-%d",
:short => "%b %d",
:long => "%B %d, %Y",
},
:day_names => Date::DAYNAMES,
:abbr_day_names => Date::ABBR_DAYNAMES,
:month_names => Date::MONTHNAMES,
:abbr_month_names => Date::ABBR_MONTHNAMES,
:order => [:year, :month, :day]
},
:time => {
:formats => {
:default => "%a, %d %b %Y %H:%M:%S %z",
:short => "%d %b %H:%M",
:long => "%B %d, %Y %H:%M",
},
:am => 'am',
:pm => 'pm'
},
:datetime => {
:distance_in_words => {
:half_a_minute => 'half a minute',
:less_than_x_seconds => ['less than 1 second', 'less than {{count}} seconds'],
:x_seconds => ['1 second', '{{count}} seconds'],
:less_than_x_minutes => ['less than a minute', 'less than {{count}} minutes'],
:x_minutes => ['1 minute', '{{count}} minutes'],
:about_x_hours => ['about 1 hour', 'about {{count}} hours'],
:x_days => ['1 day', '{{count}} days'],
:about_x_months => ['about 1 month', 'about {{count}} months'],
:x_months => ['1 month', '{{count}} months'],
:about_x_years => ['about 1 year', 'about {{count}} year'],
:over_x_years => ['over 1 year', 'over {{count}} years']
}
},
:currency => {
:format => {
:unit => '$',
:precision => 2,
:separator => '.',
:delimiter => ',',
:format => '%u%n',
}
},
:countries => {
:names => ["Afghanistan", "Aland Islands", "Albania", "Algeria", "American Samoa", "Andorra", "Angola",
"Anguilla", "Antarctica", "Antigua And Barbuda", "Argentina", "Armenia", "Aruba", "Australia", "Austria",
"Azerbaijan", "Bahamas", "Bahrain", "Bangladesh", "Barbados", "Belarus", "Belgium", "Belize", "Benin",
"Bermuda", "Bhutan", "Bolivia", "Bosnia and Herzegowina", "Botswana", "Bouvet Island", "Brazil",
"British Indian Ocean Territory", "Brunei Darussalam", "Bulgaria", "Burkina Faso", "Burundi", "Cambodia",
"Cameroon", "Canada", "Cape Verde", "Cayman Islands", "Central African Republic", "Chad", "Chile", "China",
"Christmas Island", "Cocos (Keeling) Islands", "Colombia", "Comoros", "Congo",
"Congo, the Democratic Republic of the", "Cook Islands", "Costa Rica", "Cote d'Ivoire", "Croatia", "Cuba",
"Cyprus", "Czech Republic", "Denmark", "Djibouti", "Dominica", "Dominican Republic", "Ecuador", "Egypt",
"El Salvador", "Equatorial Guinea", "Eritrea", "Estonia", "Ethiopia", "Falkland Islands (Malvinas)",
"Faroe Islands", "Fiji", "Finland", "France", "French Guiana", "French Polynesia",
"French Southern Territories", "Gabon", "Gambia", "Georgia", "Germany", "Ghana", "Gibraltar", "Greece",
"Greenland", "Grenada", "Guadeloupe", "Guam", "Guatemala", "Guernsey", "Guinea",
"Guinea-Bissau", "Guyana", "Haiti", "Heard and McDonald Islands", "Holy See (Vatican City State)",
"Honduras", "Hong Kong", "Hungary", "Iceland", "India", "Indonesia", "Iran, Islamic Republic of", "Iraq",
"Ireland", "Isle of Man", "Israel", "Italy", "Jamaica", "Japan", "Jersey", "Jordan", "Kazakhstan", "Kenya",
"Kiribati", "Korea, Democratic People's Republic of", "Korea, Republic of", "Kuwait", "Kyrgyzstan",
"Lao People's Democratic Republic", "Latvia", "Lebanon", "Lesotho", "Liberia", "Libyan Arab Jamahiriya",
"Liechtenstein", "Lithuania", "Luxembourg", "Macao", "Macedonia, The Former Yugoslav Republic Of",
"Madagascar", "Malawi", "Malaysia", "Maldives", "Mali", "Malta", "Marshall Islands", "Martinique",
"Mauritania", "Mauritius", "Mayotte", "Mexico", "Micronesia, Federated States of", "Moldova, Republic of",
"Monaco", "Mongolia", "Montenegro", "Montserrat", "Morocco", "Mozambique", "Myanmar", "Namibia", "Nauru",
"Nepal", "Netherlands", "Netherlands Antilles", "New Caledonia", "New Zealand", "Nicaragua", "Niger",
"Nigeria", "Niue", "Norfolk Island", "Northern Mariana Islands", "Norway", "Oman", "Pakistan", "Palau",
"Palestinian Territory, Occupied", "Panama", "Papua New Guinea", "Paraguay", "Peru", "Philippines",
"Pitcairn", "Poland", "Portugal", "Puerto Rico", "Qatar", "Reunion", "Romania", "Russian Federation",
"Rwanda", "Saint Barthelemy", "Saint Helena", "Saint Kitts and Nevis", "Saint Lucia",
"Saint Pierre and Miquelon", "Saint Vincent and the Grenadines", "Samoa", "San Marino",
"Sao Tome and Principe", "Saudi Arabia", "Senegal", "Serbia", "Seychelles", "Sierra Leone", "Singapore",
"Slovakia", "Slovenia", "Solomon Islands", "Somalia", "South Africa",
"South Georgia and the South Sandwich Islands", "Spain", "Sri Lanka", "Sudan", "Suriname",
"Svalbard and Jan Mayen", "Swaziland", "Sweden", "Switzerland", "Syrian Arab Republic",
"Taiwan, Province of China", "Tajikistan", "Tanzania, United Republic of", "Thailand", "Timor-Leste",
"Togo", "Tokelau", "Tonga", "Trinidad and Tobago", "Tunisia", "Turkey", "Turkmenistan",
"Turks and Caicos Islands", "Tuvalu", "Uganda", "Ukraine", "United Arab Emirates", "United Kingdom",
"United States", "United States Minor Outlying Islands", "Uruguay", "Uzbekistan", "Vanuatu", "Venezuela",
"Viet Nam", "Virgin Islands, British", "Virgin Islands, U.S.", "Wallis and Futuna", "Western Sahara",
"Yemen", "Zambia", "Zimbabwe"]
},
:active_record => {
:error => {
:header_message => ["1 error prohibited this {{object_name}} from being saved", "{{count}} errors prohibited this {{object_name}} from being saved"],
:message => "There were problems with the following fields:"
}
}
}

View File

@ -0,0 +1,47 @@
require 'abstract_unit'
class ActiveRecordHelperI18nTest < Test::Unit::TestCase
include ActionView::Helpers::ActiveRecordHelper
attr_reader :request
def setup
@request = mock
@object = stub :errors => stub(:count => 1, :full_messages => ['full_messages'])
stubs(:content_tag).returns 'content_tag'
I18n.stubs(:t).with(:'header_message', :locale => 'en-US', :scope => [:active_record, :error], :count => 1, :object_name => '').returns "1 error prohibited this from being saved"
I18n.stubs(:t).with(:'message', :locale => 'en-US', :scope => [:active_record, :error]).returns 'There were problems with the following fields:'
end
def test_error_messages_for_given_a_locale_it_does_not_check_request_for_locale
request.expects(:locale).never
@object.errors.stubs(:count).returns 0
error_messages_for(:object => @object, :locale => 'en-US')
end
def test_error_messages_for_given_no_locale_it_checks_request_for_locale
request.expects(:locale).returns 'en-US'
@object.errors.stubs(:count).returns 0
error_messages_for(:object => @object)
end
def test_error_messages_for_given_a_header_message_option_it_does_not_translate_header_message
I18n.expects(:translate).with(:'header_message', :locale => 'en-US', :scope => [:active_record, :error], :count => 1, :object_name => '').never
error_messages_for(:object => @object, :header_message => 'header message', :locale => 'en-US')
end
def test_error_messages_for_given_no_header_message_option_it_translates_header_message
I18n.expects(:t).with(:'header_message', :locale => 'en-US', :scope => [:active_record, :error], :count => 1, :object_name => '').returns 'header message'
error_messages_for(:object => @object, :locale => 'en-US')
end
def test_error_messages_for_given_a_message_option_it_does_not_translate_message
I18n.expects(:t).with(:'message', :locale => 'en-US', :scope => [:active_record, :error]).never
error_messages_for(:object => @object, :message => 'message', :locale => 'en-US')
end
def test_error_messages_for_given_no_message_option_it_translates_message
I18n.expects(:t).with(:'message', :locale => 'en-US', :scope => [:active_record, :error]).returns 'There were problems with the following fields:'
error_messages_for(:object => @object, :locale => 'en-US')
end
end

View File

@ -0,0 +1,99 @@
require 'abstract_unit'
class DateHelperDistanceOfTimeInWordsI18nTests < Test::Unit::TestCase
include ActionView::Helpers::DateHelper
attr_reader :request
def setup
@request = mock
@from = Time.mktime(2004, 6, 6, 21, 45, 0)
end
# distance_of_time_in_words
def test_distance_of_time_in_words_given_a_locale_it_does_not_check_request_for_locale
request.expects(:locale).never
distance_of_time_in_words @from, @from + 1.second, false, :locale => 'en-US'
end
def test_distance_of_time_in_words_given_no_locale_it_checks_request_for_locale
request.expects(:locale).returns 'en-US'
distance_of_time_in_words @from, @from + 1.second
end
def test_distance_of_time_in_words_calls_i18n
{ # with include_seconds
[2.seconds, true] => [:'less_than_x_seconds', 5],
[9.seconds, true] => [:'less_than_x_seconds', 10],
[19.seconds, true] => [:'less_than_x_seconds', 20],
[30.seconds, true] => [:'half_a_minute', nil],
[59.seconds, true] => [:'less_than_x_minutes', 1],
[60.seconds, true] => [:'x_minutes', 1],
# without include_seconds
[29.seconds, false] => [:'less_than_x_minutes', 1],
[60.seconds, false] => [:'x_minutes', 1],
[44.minutes, false] => [:'x_minutes', 44],
[61.minutes, false] => [:'about_x_hours', 1],
[24.hours, false] => [:'x_days', 1],
[30.days, false] => [:'about_x_months', 1],
[60.days, false] => [:'x_months', 2],
[1.year, false] => [:'about_x_years', 1],
[3.years, false] => [:'over_x_years', 3]
}.each do |passed, expected|
assert_distance_of_time_in_words_translates_key passed, expected
end
end
def assert_distance_of_time_in_words_translates_key(passed, expected)
diff, include_seconds = *passed
key, count = *expected
to = @from + diff
options = {:locale => 'en-US', :scope => :'datetime.distance_in_words'}
options[:count] = count if count
I18n.expects(:t).with(key, options)
distance_of_time_in_words(@from, to, include_seconds, :locale => 'en-US')
end
end
class DateHelperSelectTagsI18nTests < Test::Unit::TestCase
include ActionView::Helpers::DateHelper
attr_reader :request
def setup
@request = mock
I18n.stubs(:translate).with(:'date.month_names', 'en-US').returns Date::MONTHNAMES
end
# select_month
def test_select_month_given_use_month_names_option_does_not_translate_monthnames
I18n.expects(:translate).never
select_month(8, :locale => 'en-US', :use_month_names => Date::MONTHNAMES)
end
def test_select_month_translates_monthnames
I18n.expects(:translate).with(:'date.month_names', 'en-US').returns Date::MONTHNAMES
select_month(8, :locale => 'en-US')
end
def test_select_month_given_use_short_month_option_translates_abbr_monthnames
I18n.expects(:translate).with(:'date.abbr_month_names', 'en-US').returns Date::ABBR_MONTHNAMES
select_month(8, :locale => 'en-US', :use_short_month => true)
end
# date_or_time_select
def test_date_or_time_select_given_an_order_options_does_not_translate_order
I18n.expects(:translate).never
datetime_select('post', 'updated_at', :order => [:year, :month, :day], :locale => 'en-US')
end
def test_date_or_time_select_given_no_order_options_translates_order
I18n.expects(:translate).with(:'date.order', 'en-US').returns [:year, :month, :day]
datetime_select('post', 'updated_at', :locale => 'en-US')
end
end

View File

@ -0,0 +1,26 @@
require 'abstract_unit'
class FormOptionsHelperI18nTests < Test::Unit::TestCase
include ActionView::Helpers::FormOptionsHelper
attr_reader :request
def setup
@request = mock
end
def test_country_options_for_select_given_a_locale_it_does_not_check_request_for_locale
request.expects(:locale).never
country_options_for_select :locale => 'en-US'
end
def test_country_options_for_select_given_no_locale_it_checks_request_for_locale
request.expects(:locale).returns 'en-US'
country_options_for_select
end
def test_country_options_for_select_translates_country_names
countries = ActionView::Helpers::FormOptionsHelper::COUNTRIES
I18n.expects(:translate).with(:'countries.names', 'en-US').returns countries
country_options_for_select :locale => 'en-US'
end
end

View File

@ -0,0 +1,27 @@
require 'abstract_unit'
class NumberHelperI18nTests < Test::Unit::TestCase
include ActionView::Helpers::NumberHelper
attr_reader :request
def setup
@request = mock
@defaults = {:separator => ".", :unit => "$", :format => "%u%n", :delimiter => ",", :precision => 2}
I18n.backend.add_translations 'en-US', :currency => {:format => @defaults}
end
def test_number_to_currency_given_a_locale_it_does_not_check_request_for_locale
request.expects(:locale).never
number_to_currency(1, :locale => 'en-US')
end
def test_number_to_currency_given_no_locale_it_checks_request_for_locale
request.expects(:locale).returns 'en-US'
number_to_currency(1)
end
def test_number_to_currency_translates_currency_formats
I18n.expects(:translate).with(:'currency.format', 'en-US').returns @defaults
number_to_currency(1, :locale => 'en-US')
end
end

View File

@ -80,3 +80,5 @@ end
require 'active_record/connection_adapters/abstract_adapter'
require 'active_record/schema_dumper'
require 'active_record/lang/en-US.rb'

View File

@ -0,0 +1,25 @@
I18n.backend.add_translations :'en-US', {
:active_record => {
:error_messages => {
:inclusion => "is not included in the list",
:exclusion => "is reserved",
:invalid => "is invalid",
:confirmation => "doesn't match confirmation",
:accepted => "must be accepted",
:empty => "can't be empty",
:blank => "can't be blank",
:too_long => "is too long (maximum is {{count}} characters)",
:too_short => "is too short (minimum is {{count}} characters)",
:wrong_length => "is the wrong length (should be {{count}} characters)",
:taken => "has already been taken",
:not_a_number => "is not a number",
:greater_than => "must be greater than {{count}}",
:greater_than_or_equal_to => "must be greater than or equal to {{count}}",
:equal_to => "must be equal to {{count}}",
:less_than => "must be less than {{count}}",
:less_than_or_equal_to => "must be less than or equal to {{count}}",
:odd => "must be odd",
:even => "must be even"
}
}
}

View File

@ -23,30 +23,30 @@ module ActiveRecord
@base, @errors = base, {}
end
@@default_error_messages = {
:inclusion => "is not included in the list",
:exclusion => "is reserved",
:invalid => "is invalid",
:confirmation => "doesn't match confirmation",
:accepted => "must be accepted",
:empty => "can't be empty",
:blank => "can't be blank",
:too_long => "is too long (maximum is %d characters)",
:too_short => "is too short (minimum is %d characters)",
:wrong_length => "is the wrong length (should be %d characters)",
:taken => "has already been taken",
:not_a_number => "is not a number",
:greater_than => "must be greater than %d",
:greater_than_or_equal_to => "must be greater than or equal to %d",
:equal_to => "must be equal to %d",
:less_than => "must be less than %d",
:less_than_or_equal_to => "must be less than or equal to %d",
:odd => "must be odd",
:even => "must be even"
}
# Holds a hash with all the default error messages that can be replaced by your own copy or localizations.
cattr_accessor :default_error_messages
# @@default_error_messages = {
# :inclusion => "is not included in the list",
# :exclusion => "is reserved",
# :invalid => "is invalid",
# :confirmation => "doesn't match confirmation",
# :accepted => "must be accepted",
# :empty => "can't be empty",
# :blank => "can't be blank",
# :too_long => "is too long (maximum is %d characters)",
# :too_short => "is too short (minimum is %d characters)",
# :wrong_length => "is the wrong length (should be %d characters)",
# :taken => "has already been taken",
# :not_a_number => "is not a number",
# :greater_than => "must be greater than %d",
# :greater_than_or_equal_to => "must be greater than or equal to %d",
# :equal_to => "must be equal to %d",
# :less_than => "must be less than %d",
# :less_than_or_equal_to => "must be less than or equal to %d",
# :odd => "must be odd",
# :even => "must be even"
# }
#
# # Holds a hash with all the default error messages that can be replaced by your own copy or localizations.
# cattr_accessor :default_error_messages
# Adds an error to the base object instead of any particular attribute. This is used
@ -61,27 +61,34 @@ module ActiveRecord
# for the same attribute and ensure that this error object returns false when asked if <tt>empty?</tt>. More than one
# error can be added to the same +attribute+ in which case an array will be returned on a call to <tt>on(attribute)</tt>.
# If no +msg+ is supplied, "invalid" is assumed.
def add(attribute, msg = @@default_error_messages[:invalid])
@errors[attribute.to_s] = [] if @errors[attribute.to_s].nil?
@errors[attribute.to_s] << msg
end
def add(attribute, message = nil)
message ||= :"active_record.error_messages.invalid".t
@errors[attribute.to_s] ||= []
@errors[attribute.to_s] << message
end
# Will add an error message to each of the attributes in +attributes+ that is empty.
def add_on_empty(attributes, msg = @@default_error_messages[:empty])
def add_on_empty(attributes, custom_message = nil)
for attr in [attributes].flatten
value = @base.respond_to?(attr.to_s) ? @base.send(attr.to_s) : @base[attr.to_s]
is_empty = value.respond_to?("empty?") ? value.empty? : false
add(attr, msg) unless !value.nil? && !is_empty
is_empty = value.respond_to?("empty?") ? value.empty? : false
add(attr, generate_message(attr, :empty, :default => custom_message)) unless !value.nil? && !is_empty
end
end
# Will add an error message to each of the attributes in +attributes+ that is blank (using Object#blank?).
def add_on_blank(attributes, msg = @@default_error_messages[:blank])
def add_on_blank(attributes, custom_message = nil)
for attr in [attributes].flatten
value = @base.respond_to?(attr.to_s) ? @base.send(attr.to_s) : @base[attr.to_s]
add(attr, msg) if value.blank?
add(attr, generate_message(attr, :blank, :default => custom_message)) if value.blank?
end
end
def generate_message(attr, key, options = {})
scope = [:active_record, :error_messages]
key.t(options.merge(:scope => scope + [:custom, @base.class.name.downcase, attr])) ||
key.t(options.merge(:scope => scope))
end
# Returns true if the specified +attribute+ has errors associated with it.
#
@ -166,22 +173,25 @@ module ActiveRecord
# company = Company.create(:address => '123 First St.')
# company.errors.full_messages # =>
# ["Name is too short (minimum is 5 characters)", "Name can't be blank", "Address can't be blank"]
def full_messages
def full_messages(options = {})
full_messages = []
locale = options[:locale]
@errors.each_key do |attr|
@errors[attr].each do |msg|
next if msg.nil?
@errors[attr].each do |message|
next unless message
if attr == "base"
full_messages << msg
full_messages << message
else
full_messages << @base.class.human_attribute_name(attr) + " " + msg
key = :"active_record.human_attribute_names.#{@base.class.name.underscore.to_sym}.#{attr}"
attr_name = key.t(locale) || @base.class.human_attribute_name(attr)
full_messages << attr_name + " " + message
end
end
end
full_messages
end
end
# Returns true if no errors have been added.
def empty?
@ -388,15 +398,18 @@ module ActiveRecord
# not occur (e.g. <tt>:unless => :skip_validation</tt>, or <tt>:unless => Proc.new { |user| user.signup_step <= 2 }</tt>). The
# method, proc or string should return or evaluate to a true or false value.
def validates_confirmation_of(*attr_names)
configuration = { :message => ActiveRecord::Errors.default_error_messages[:confirmation], :on => :save }
configuration = { :on => :save }
configuration.update(attr_names.extract_options!)
attr_accessor(*(attr_names.map { |n| "#{n}_confirmation" }))
validates_each(attr_names, configuration) do |record, attr_name, value|
record.errors.add(attr_name, configuration[:message]) unless record.send("#{attr_name}_confirmation").nil? or value == record.send("#{attr_name}_confirmation")
unless record.send("#{attr_name}_confirmation").nil? or value == record.send("#{attr_name}_confirmation")
message = record.errors.generate_message(attr_name, :confirmation, :default => configuration[:message])
record.errors.add(attr_name, message)
end
end
end
end
# Encapsulates the pattern of wanting to validate the acceptance of a terms of service check box (or similar agreement). Example:
#
@ -422,7 +435,7 @@ module ActiveRecord
# not occur (e.g. <tt>:unless => :skip_validation</tt>, or <tt>:unless => Proc.new { |user| user.signup_step <= 2 }</tt>). The
# method, proc or string should return or evaluate to a true or false value.
def validates_acceptance_of(*attr_names)
configuration = { :message => ActiveRecord::Errors.default_error_messages[:accepted], :on => :save, :allow_nil => true, :accept => "1" }
configuration = { :on => :save, :allow_nil => true, :accept => "1" }
configuration.update(attr_names.extract_options!)
db_cols = begin
@ -434,7 +447,10 @@ module ActiveRecord
attr_accessor(*names)
validates_each(attr_names,configuration) do |record, attr_name, value|
record.errors.add(attr_name, configuration[:message]) unless value == configuration[:accept]
unless value == configuration[:accept]
message = record.errors.generate_message(attr_name, :accepted, :default => configuration[:message])
record.errors.add(attr_name, message)
end
end
end
@ -461,7 +477,7 @@ module ActiveRecord
# method, proc or string should return or evaluate to a true or false value.
#
def validates_presence_of(*attr_names)
configuration = { :message => ActiveRecord::Errors.default_error_messages[:blank], :on => :save }
configuration = { :on => :save }
configuration.update(attr_names.extract_options!)
# can't use validates_each here, because it cannot cope with nonexistent attributes,
@ -505,11 +521,7 @@ module ActiveRecord
# method, proc or string should return or evaluate to a true or false value.
def validates_length_of(*attrs)
# Merge given options with defaults.
options = {
:too_long => ActiveRecord::Errors.default_error_messages[:too_long],
:too_short => ActiveRecord::Errors.default_error_messages[:too_short],
:wrong_length => ActiveRecord::Errors.default_error_messages[:wrong_length]
}.merge(DEFAULT_VALIDATION_OPTIONS)
options = {}.merge(DEFAULT_VALIDATION_OPTIONS)
options.update(attrs.extract_options!.symbolize_keys)
# Ensure that one and only one range option is specified.
@ -531,15 +543,14 @@ module ActiveRecord
when :within, :in
raise ArgumentError, ":#{option} must be a Range" unless option_value.is_a?(Range)
too_short = options[:too_short] % option_value.begin
too_long = options[:too_long] % option_value.end
validates_each(attrs, options) do |record, attr, value|
value = value.split(//) if value.kind_of?(String)
if value.nil? or value.size < option_value.begin
record.errors.add(attr, too_short)
message = record.errors.generate_message(attr, :too_short, :default => options[:too_short], :count => option_value.begin)
record.errors.add(attr, message)
elsif value.size > option_value.end
record.errors.add(attr, too_long)
message = record.errors.generate_message(attr, :too_long, :default => options[:too_long], :count => option_value.end)
record.errors.add(attr, message)
end
end
when :is, :minimum, :maximum
@ -549,11 +560,14 @@ module ActiveRecord
validity_checks = { :is => "==", :minimum => ">=", :maximum => "<=" }
message_options = { :is => :wrong_length, :minimum => :too_short, :maximum => :too_long }
message = (options[:message] || options[message_options[option]]) % option_value
validates_each(attrs, options) do |record, attr, value|
value = value.split(//) if value.kind_of?(String)
record.errors.add(attr, message) unless !value.nil? and value.size.method(validity_checks[option])[option_value]
unless !value.nil? and value.size.method(validity_checks[option])[option_value]
key = message_options[option]
custom_message = options[:message] || options[key]
message = record.errors.generate_message(attr, key, :default => custom_message, :count => option_value)
record.errors.add(attr, message)
end
end
end
end
@ -595,7 +609,7 @@ module ActiveRecord
# not occur (e.g. <tt>:unless => :skip_validation</tt>, or <tt>:unless => Proc.new { |user| user.signup_step <= 2 }</tt>). The
# method, proc or string should return or evaluate to a true or false value.
def validates_uniqueness_of(*attr_names)
configuration = { :message => ActiveRecord::Errors.default_error_messages[:taken], :case_sensitive => true }
configuration = { :case_sensitive => true }
configuration.update(attr_names.extract_options!)
validates_each(attr_names,configuration) do |record, attr_name, value|
@ -654,8 +668,11 @@ module ActiveRecord
if configuration[:case_sensitive] && finder_class.columns_hash[attr_name.to_s].text?
found = results.any? { |a| a[attr_name.to_s] == value }
end
record.errors.add(attr_name, configuration[:message]) if found
if found
message = record.errors.generate_message(attr_name, :taken, :default => configuration[:message])
record.errors.add(attr_name, message)
end
end
end
end
@ -685,13 +702,16 @@ module ActiveRecord
# not occur (e.g. <tt>:unless => :skip_validation</tt>, or <tt>:unless => Proc.new { |user| user.signup_step <= 2 }</tt>). The
# method, proc or string should return or evaluate to a true or false value.
def validates_format_of(*attr_names)
configuration = { :message => ActiveRecord::Errors.default_error_messages[:invalid], :on => :save, :with => nil }
configuration = { :on => :save, :with => nil }
configuration.update(attr_names.extract_options!)
raise(ArgumentError, "A regular expression must be supplied as the :with option of the configuration hash") unless configuration[:with].is_a?(Regexp)
validates_each(attr_names, configuration) do |record, attr_name, value|
record.errors.add(attr_name, configuration[:message] % value) unless value.to_s =~ configuration[:with]
unless value.to_s =~ configuration[:with]
message = record.errors.generate_message(attr_name, :invalid, :default => configuration[:message], :value => value)
record.errors.add(attr_name, message)
end
end
end
@ -715,7 +735,7 @@ module ActiveRecord
# not occur (e.g. <tt>:unless => :skip_validation</tt>, or <tt>:unless => Proc.new { |user| user.signup_step <= 2 }</tt>). The
# method, proc or string should return or evaluate to a true or false value.
def validates_inclusion_of(*attr_names)
configuration = { :message => ActiveRecord::Errors.default_error_messages[:inclusion], :on => :save }
configuration = { :on => :save, :with => nil }
configuration.update(attr_names.extract_options!)
enum = configuration[:in] || configuration[:within]
@ -723,7 +743,10 @@ module ActiveRecord
raise(ArgumentError, "An object with the method include? is required must be supplied as the :in option of the configuration hash") unless enum.respond_to?("include?")
validates_each(attr_names, configuration) do |record, attr_name, value|
record.errors.add(attr_name, configuration[:message] % value) unless enum.include?(value)
unless enum.include?(value)
message = record.errors.generate_message(attr_name, :inclusion, :default => configuration[:message], :value => value)
record.errors.add(attr_name, message)
end
end
end
@ -747,7 +770,7 @@ module ActiveRecord
# not occur (e.g. <tt>:unless => :skip_validation</tt>, or <tt>:unless => Proc.new { |user| user.signup_step <= 2 }</tt>). The
# method, proc or string should return or evaluate to a true or false value.
def validates_exclusion_of(*attr_names)
configuration = { :message => ActiveRecord::Errors.default_error_messages[:exclusion], :on => :save }
configuration = { :on => :save, :with => nil }
configuration.update(attr_names.extract_options!)
enum = configuration[:in] || configuration[:within]
@ -755,7 +778,10 @@ module ActiveRecord
raise(ArgumentError, "An object with the method include? is required must be supplied as the :in option of the configuration hash") unless enum.respond_to?("include?")
validates_each(attr_names, configuration) do |record, attr_name, value|
record.errors.add(attr_name, configuration[:message] % value) if enum.include?(value)
if enum.include?(value)
message = record.errors.generate_message(attr_name, :exclusion, :default => configuration[:message], :value => value)
record.errors.add(attr_name, message)
end
end
end
@ -791,12 +817,14 @@ module ActiveRecord
# not occur (e.g. <tt>:unless => :skip_validation</tt>, or <tt>:unless => Proc.new { |user| user.signup_step <= 2 }</tt>). The
# method, proc or string should return or evaluate to a true or false value.
def validates_associated(*attr_names)
configuration = { :message => ActiveRecord::Errors.default_error_messages[:invalid], :on => :save }
configuration = { :on => :save }
configuration.update(attr_names.extract_options!)
validates_each(attr_names, configuration) do |record, attr_name, value|
record.errors.add(attr_name, configuration[:message]) unless
(value.is_a?(Array) ? value : [value]).inject(true) { |v, r| (r.nil? || r.valid?) && v }
unless (value.is_a?(Array) ? value : [value]).inject(true) { |v, r| (r.nil? || r.valid?) && v }
message = record.errors.generate_message(attr_name, :invalid, :default => configuration[:message], :value => value)
record.errors.add(attr_name, message)
end
end
end
@ -844,7 +872,8 @@ module ActiveRecord
if configuration[:only_integer]
unless raw_value.to_s =~ /\A[+-]?\d+\Z/
record.errors.add(attr_name, configuration[:message] || ActiveRecord::Errors.default_error_messages[:not_a_number])
message = record.errors.generate_message(attr_name, :not_a_number, :value => raw_value, :default => configuration[:message])
record.errors.add(attr_name, message)
next
end
raw_value = raw_value.to_i
@ -852,7 +881,8 @@ module ActiveRecord
begin
raw_value = Kernel.Float(raw_value.to_s)
rescue ArgumentError, TypeError
record.errors.add(attr_name, configuration[:message] || ActiveRecord::Errors.default_error_messages[:not_a_number])
message = record.errors.generate_message(attr_name, :not_a_number, :value => raw_value, :default => configuration[:message])
record.errors.add(attr_name, message)
next
end
end
@ -860,10 +890,12 @@ module ActiveRecord
numericality_options.each do |option|
case option
when :odd, :even
record.errors.add(attr_name, configuration[:message] || ActiveRecord::Errors.default_error_messages[option]) unless raw_value.to_i.method(ALL_NUMERICALITY_CHECKS[option])[]
unless raw_value.to_i.method(ALL_NUMERICALITY_CHECKS[option])[]
message = record.errors.generate_message(attr_name, option, :value => raw_value, :default => configuration[:message])
record.errors.add(attr_name, message)
end
else
message = configuration[:message] || ActiveRecord::Errors.default_error_messages[option]
message = message % configuration[option] if configuration[option]
message = record.errors.generate_message(attr_name, option, :default => configuration[:message], :value => raw_value, :count => configuration[option])
record.errors.add(attr_name, message) unless raw_value.method(ALL_NUMERICALITY_CHECKS[option])[configuration[option]]
end
end

View File

@ -0,0 +1,539 @@
require "cases/helper"
require 'models/topic'
require 'models/reply'
class ActiveRecordValidationsI18nTests < Test::Unit::TestCase
def setup
reset_callbacks Topic
@topic = Topic.new
I18n.backend.add_translations('en-US', :active_record => {:error_messages => {:custom => nil}})
end
def teardown
reset_callbacks Topic
load 'active_record/lang/en-US.rb'
end
def unique_topic
@unique ||= Topic.create :title => 'unique!'
end
def replied_topic
@replied_topic ||= begin
topic = Topic.create(:title => "topic")
topic.replies << Reply.new
topic
end
end
def reset_callbacks(*models)
models.each do |model|
model.instance_variable_set("@validate_callbacks", ActiveSupport::Callbacks::CallbackChain.new)
model.instance_variable_set("@validate_on_create_callbacks", ActiveSupport::Callbacks::CallbackChain.new)
model.instance_variable_set("@validate_on_update_callbacks", ActiveSupport::Callbacks::CallbackChain.new)
end
end
# ActiveRecord::Errors
def test_errors_generate_message_translates_custom_model_attribute_key
global_scope = [:active_record, :error_messages]
custom_scope = global_scope + [:custom, 'topic', :title]
I18n.expects(:translate).with(:invalid, :scope => custom_scope).returns 'translation'
I18n.expects(:translate).with(:invalid, :scope => global_scope).never
@topic.errors.generate_message :title, :invalid
end
def test_errors_generate_message_given_a_custom_message_translates_custom_model_attribute_key_with_custom_message_as_default
custom_scope = [:active_record, :error_messages, :custom, 'topic', :title]
I18n.expects(:translate).with(:invalid, :scope => custom_scope, :default => 'default from class def').returns 'translation'
@topic.errors.generate_message :title, :invalid, :default => 'default from class def'
end
def test_errors_generate_message_given_no_custom_message_falls_back_to_global_default_key_translation
global_scope = [:active_record, :error_messages]
custom_scope = global_scope + [:custom, 'topic', :title]
I18n.stubs(:translate).with(:invalid, :scope => custom_scope).returns nil
I18n.expects(:translate).with(:invalid, :scope => global_scope)
@topic.errors.generate_message :title, :invalid
end
def test_errors_add_given_no_message_it_translates_invalid
I18n.expects(:translate).with(:"active_record.error_messages.invalid")
@topic.errors.add :title
end
def test_errors_add_on_empty_generates_message
@topic.errors.expects(:generate_message).with(:title, :empty, {:default => nil})
@topic.errors.add_on_empty :title
end
def test_errors_add_on_empty_generates_message_with_custom_default_message
@topic.errors.expects(:generate_message).with(:title, :empty, {:default => 'custom'})
@topic.errors.add_on_empty :title, 'custom'
end
def test_errors_add_on_blank_generates_message
@topic.errors.expects(:generate_message).with(:title, :blank, {:default => nil})
@topic.errors.add_on_blank :title
end
def test_errors_add_on_blank_generates_message_with_custom_default_message
@topic.errors.expects(:generate_message).with(:title, :blank, {:default => 'custom'})
@topic.errors.add_on_blank :title, 'custom'
end
def test_errors_full_messages_translates_human_attribute_name_for_model_attributes
@topic.errors.instance_variable_set :@errors, { 'title' => 'empty' }
I18n.expects(:translate).with(:"active_record.human_attribute_names.topic.title", 'en-US').returns('Title')
@topic.errors.full_messages :locale => 'en-US'
end
# ActiveRecord::Validations
# validates_confirmation_of
def test_validates_confirmation_of_generates_message
Topic.validates_confirmation_of :title
@topic.title_confirmation = 'foo'
@topic.errors.expects(:generate_message).with(:title, :confirmation, {:default => nil})
@topic.valid?
end
def test_validates_confirmation_of_generates_message_with_custom_default_message
Topic.validates_confirmation_of :title, :message => 'custom'
@topic.title_confirmation = 'foo'
@topic.errors.expects(:generate_message).with(:title, :confirmation, {:default => 'custom'})
@topic.valid?
end
def test_validates_confirmation_of_finds_custom_model_key_translation
I18n.backend.add_translations 'en-US', :active_record => {:error_messages => {:custom => {:topic => {:title => {:confirmation => 'custom message'}}}}}
I18n.backend.add_translations 'en-US', :active_record => {:error_messages => {:confirmation => 'global message'}}
Topic.validates_confirmation_of :title
@topic.title_confirmation = 'foo'
@topic.valid?
assert_equal 'custom message', @topic.errors.on(:title)
end
def test_validates_confirmation_of_finds_global_default_translation
I18n.backend.add_translations 'en-US', :active_record => {:error_messages => {:confirmation => 'global message'}}
Topic.validates_confirmation_of :title
@topic.title_confirmation = 'foo'
@topic.valid?
assert_equal 'global message', @topic.errors.on(:title)
end
# validates_acceptance_of
def test_validates_acceptance_of_generates_message
Topic.validates_acceptance_of :title, :allow_nil => false
@topic.errors.expects(:generate_message).with(:title, :accepted, {:default => nil})
@topic.valid?
end
def test_validates_acceptance_of_generates_message_with_custom_default_message
Topic.validates_acceptance_of :title, :message => 'custom', :allow_nil => false
@topic.errors.expects(:generate_message).with(:title, :accepted, {:default => 'custom'})
@topic.valid?
end
def test_validates_acceptance_of_finds_custom_model_key_translation
I18n.backend.add_translations 'en-US', :active_record => {:error_messages => {:custom => {:topic => {:title => {:accepted => 'custom message'}}}}}
I18n.backend.add_translations 'en-US', :active_record => {:error_messages => {:accepted => 'global message'}}
Topic.validates_acceptance_of :title, :allow_nil => false
@topic.valid?
assert_equal 'custom message', @topic.errors.on(:title)
end
def test_validates_acceptance_of_finds_global_default_translation
I18n.backend.add_translations 'en-US', :active_record => {:error_messages => {:accepted => 'global message'}}
Topic.validates_acceptance_of :title, :allow_nil => false
@topic.valid?
assert_equal 'global message', @topic.errors.on(:title)
end
# validates_presence_of
def test_validates_presence_of_generates_message
Topic.validates_presence_of :title
@topic.errors.expects(:generate_message).with(:title, :blank, {:default => nil})
@topic.valid?
end
def test_validates_presence_of_generates_message_with_custom_default_message
Topic.validates_presence_of :title, :message => 'custom'
@topic.errors.expects(:generate_message).with(:title, :blank, {:default => 'custom'})
@topic.valid?
end
def test_validates_presence_of_finds_custom_model_key_translation
I18n.backend.add_translations 'en-US', :active_record => {:error_messages => {:custom => {:topic => {:title => {:blank => 'custom message'}}}}}
I18n.backend.add_translations 'en-US', :active_record => {:error_messages => {:blank => 'global message'}}
Topic.validates_presence_of :title
@topic.valid?
assert_equal 'custom message', @topic.errors.on(:title)
end
def test_validates_presence_of_finds_global_default_translation
I18n.backend.add_translations 'en-US', :active_record => {:error_messages => {:blank => 'global message'}}
Topic.validates_presence_of :title
@topic.valid?
assert_equal 'global message', @topic.errors.on(:title)
end
# validates_length_of :within
def test_validates_length_of_within_generates_message
Topic.validates_length_of :title, :within => 3..5
@topic.errors.expects(:generate_message).with(:title, :too_short, {:count => 3, :default => nil})
@topic.valid?
end
def test_validates_length_of_within_generates_message_with_custom_default_message
Topic.validates_length_of :title, :within => 3..5, :too_short => 'custom'
@topic.errors.expects(:generate_message).with(:title, :too_short, {:count => 3, :default => 'custom'})
@topic.valid?
end
def test_validates_length_of_within_finds_custom_model_key_translation
I18n.backend.add_translations 'en-US', :active_record => {:error_messages => {:custom => {:topic => {:title => {:too_short => 'custom message'}}}}}
I18n.backend.add_translations 'en-US', :active_record => {:error_messages => {:too_short => 'global message'}}
Topic.validates_length_of :title, :within => 3..5
@topic.valid?
assert_equal 'custom message', @topic.errors.on(:title)
end
def test_validates_length_of_within_finds_global_default_translation
I18n.backend.add_translations 'en-US', :active_record => {:error_messages => {:too_short => 'global message'}}
Topic.validates_length_of :title, :within => 3..5
@topic.valid?
assert_equal 'global message', @topic.errors.on(:title)
end
# validates_length_of :is
def test_validates_length_of_is_generates_message
Topic.validates_length_of :title, :is => 5
@topic.errors.expects(:generate_message).with(:title, :wrong_length, {:count => 5, :default => nil})
@topic.valid?
end
def test_validates_length_of_is_generates_message_with_custom_default_message
Topic.validates_length_of :title, :is => 5, :message => 'custom'
@topic.errors.expects(:generate_message).with(:title, :wrong_length, {:count => 5, :default => 'custom'})
@topic.valid?
end
def test_validates_length_of_within_finds_custom_model_key_translation
I18n.backend.add_translations 'en-US', :active_record => {:error_messages => {:custom => {:topic => {:title => {:wrong_length => 'custom message'}}}}}
I18n.backend.add_translations 'en-US', :active_record => {:error_messages => {:wrong_length => 'global message'}}
Topic.validates_length_of :title, :is => 5
@topic.valid?
assert_equal 'custom message', @topic.errors.on(:title)
end
def test_validates_length_of_within_finds_global_default_translation
I18n.backend.add_translations 'en-US', :active_record => {:error_messages => {:wrong_length => 'global message'}}
Topic.validates_length_of :title, :is => 5
@topic.valid?
assert_equal 'global message', @topic.errors.on(:title)
end
# validates_uniqueness_of
def test_validates_uniqueness_of_generates_message
Topic.validates_uniqueness_of :title
@topic.title = unique_topic.title
@topic.errors.expects(:generate_message).with(:title, :taken, {:default => nil})
@topic.valid?
end
def test_validates_uniqueness_of_generates_message_with_custom_default_message
Topic.validates_uniqueness_of :title, :message => 'custom'
@topic.title = unique_topic.title
@topic.errors.expects(:generate_message).with(:title, :taken, {:default => 'custom'})
@topic.valid?
end
def test_validates_length_of_within_finds_custom_model_key_translation
I18n.backend.add_translations 'en-US', :active_record => {:error_messages => {:custom => {:topic => {:title => {:wrong_length => 'custom message'}}}}}
I18n.backend.add_translations 'en-US', :active_record => {:error_messages => {:wrong_length => 'global message'}}
Topic.validates_length_of :title, :is => 5
@topic.valid?
assert_equal 'custom message', @topic.errors.on(:title)
end
def test_validates_length_of_within_finds_global_default_translation
I18n.backend.add_translations 'en-US', :active_record => {:error_messages => {:wrong_length => 'global message'}}
Topic.validates_length_of :title, :is => 5
@topic.valid?
assert_equal 'global message', @topic.errors.on(:title)
end
# validates_format_of
def test_validates_format_of_generates_message
Topic.validates_format_of :title, :with => /^[1-9][0-9]*$/
@topic.title = '72x'
@topic.errors.expects(:generate_message).with(:title, :invalid, {:value => '72x', :default => nil})
@topic.valid?
end
def test_validates_format_of_generates_message_with_custom_default_message
Topic.validates_format_of :title, :with => /^[1-9][0-9]*$/, :message => 'custom'
@topic.title = '72x'
@topic.errors.expects(:generate_message).with(:title, :invalid, {:value => '72x', :default => 'custom'})
@topic.valid?
end
def test_validates_format_of_finds_custom_model_key_translation
I18n.backend.add_translations 'en-US', :active_record => {:error_messages => {:custom => {:topic => {:title => {:invalid => 'custom message'}}}}}
I18n.backend.add_translations 'en-US', :active_record => {:error_messages => {:invalid => 'global message'}}
Topic.validates_format_of :title, :with => /^[1-9][0-9]*$/
@topic.valid?
assert_equal 'custom message', @topic.errors.on(:title)
end
def test_validates_format_of_finds_global_default_translation
I18n.backend.add_translations 'en-US', :active_record => {:error_messages => {:invalid => 'global message'}}
Topic.validates_format_of :title, :with => /^[1-9][0-9]*$/
@topic.valid?
assert_equal 'global message', @topic.errors.on(:title)
end
# validates_inclusion_of
def test_validates_inclusion_of_generates_message
Topic.validates_inclusion_of :title, :in => %w(a b c)
@topic.title = 'z'
@topic.errors.expects(:generate_message).with(:title, :inclusion, {:value => 'z', :default => nil})
@topic.valid?
end
def test_validates_inclusion_of_generates_message_with_custom_default_message
Topic.validates_inclusion_of :title, :in => %w(a b c), :message => 'custom'
@topic.title = 'z'
@topic.errors.expects(:generate_message).with(:title, :inclusion, {:value => 'z', :default => 'custom'})
@topic.valid?
end
def test_validates_inclusion_of_finds_custom_model_key_translation
I18n.backend.add_translations 'en-US', :active_record => {:error_messages => {:custom => {:topic => {:title => {:inclusion => 'custom message'}}}}}
I18n.backend.add_translations 'en-US', :active_record => {:error_messages => {:inclusion => 'global message'}}
Topic.validates_inclusion_of :title, :in => %w(a b c)
@topic.valid?
assert_equal 'custom message', @topic.errors.on(:title)
end
def test_validates_inclusion_of_finds_global_default_translation
I18n.backend.add_translations 'en-US', :active_record => {:error_messages => {:inclusion => 'global message'}}
Topic.validates_inclusion_of :title, :in => %w(a b c)
@topic.valid?
assert_equal 'global message', @topic.errors.on(:title)
end
# validates_exclusion_of
def test_validates_exclusion_of_generates_message
Topic.validates_exclusion_of :title, :in => %w(a b c)
@topic.title = 'a'
@topic.errors.expects(:generate_message).with(:title, :exclusion, {:value => 'a', :default => nil})
@topic.valid?
end
def test_validates_exclusion_of_generates_message_with_custom_default_message
Topic.validates_exclusion_of :title, :in => %w(a b c), :message => 'custom'
@topic.title = 'a'
@topic.errors.expects(:generate_message).with(:title, :exclusion, {:value => 'a', :default => 'custom'})
@topic.valid?
end
def test_validates_exclusion_of_finds_custom_model_key_translation
I18n.backend.add_translations 'en-US', :active_record => {:error_messages => {:custom => {:topic => {:title => {:exclusion => 'custom message'}}}}}
I18n.backend.add_translations 'en-US', :active_record => {:error_messages => {:exclusion => 'global message'}}
Topic.validates_exclusion_of :title, :in => %w(a b c)
@topic.title = 'a'
@topic.valid?
assert_equal 'custom message', @topic.errors.on(:title)
end
def test_validates_exclusion_of_finds_global_default_translation
I18n.backend.add_translations 'en-US', :active_record => {:error_messages => {:exclusion => 'global message'}}
Topic.validates_exclusion_of :title, :in => %w(a b c)
@topic.title = 'a'
@topic.valid?
assert_equal 'global message', @topic.errors.on(:title)
end
# validates_numericality_of :only_integer
def test_validates_numericality_of_only_integer_generates_message
Topic.validates_numericality_of :title, :only_integer => true
@topic.title = 'a'
@topic.errors.expects(:generate_message).with(:title, :not_a_number, {:value => 'a', :default => nil})
@topic.valid?
end
def test_validates_numericality_of_only_integer_generates_message_with_custom_default_message
Topic.validates_numericality_of :title, :only_integer => true, :message => 'custom'
@topic.title = 'a'
@topic.errors.expects(:generate_message).with(:title, :not_a_number, {:value => 'a', :default => 'custom'})
@topic.valid?
end
def test_validates_numericality_of_only_integer_finds_custom_model_key_translation
I18n.backend.add_translations 'en-US', :active_record => {:error_messages => {:custom => {:topic => {:title => {:not_a_number => 'custom message'}}}}}
I18n.backend.add_translations 'en-US', :active_record => {:error_messages => {:not_a_number => 'global message'}}
Topic.validates_numericality_of :title, :only_integer => true
@topic.title = 'a'
@topic.valid?
assert_equal 'custom message', @topic.errors.on(:title)
end
def test_validates_numericality_of_only_integer_finds_global_default_translation
I18n.backend.add_translations 'en-US', :active_record => {:error_messages => {:not_a_number => 'global message'}}
Topic.validates_numericality_of :title, :only_integer => true
@topic.title = 'a'
@topic.valid?
assert_equal 'global message', @topic.errors.on(:title)
end
# validates_numericality_of :odd
def test_validates_numericality_of_odd_generates_message
Topic.validates_numericality_of :title, :only_integer => true, :odd => true
@topic.title = 0
@topic.errors.expects(:generate_message).with(:title, :odd, {:value => 0, :default => nil})
@topic.valid?
end
def test_validates_numericality_of_odd_generates_message_with_custom_default_message
Topic.validates_numericality_of :title, :only_integer => true, :odd => true, :message => 'custom'
@topic.title = 0
@topic.errors.expects(:generate_message).with(:title, :odd, {:value => 0, :default => 'custom'})
@topic.valid?
end
def test_validates_numericality_of_odd_finds_custom_model_key_translation
I18n.backend.add_translations 'en-US', :active_record => {:error_messages => {:custom => {:topic => {:title => {:odd => 'custom message'}}}}}
I18n.backend.add_translations 'en-US', :active_record => {:error_messages => {:odd => 'global message'}}
Topic.validates_numericality_of :title, :only_integer => true, :odd => true
@topic.title = 0
@topic.valid?
assert_equal 'custom message', @topic.errors.on(:title)
end
def test_validates_numericality_of_odd_finds_global_default_translation
I18n.backend.add_translations 'en-US', :active_record => {:error_messages => {:odd => 'global message'}}
Topic.validates_numericality_of :title, :only_integer => true, :odd => true
@topic.title = 0
@topic.valid?
assert_equal 'global message', @topic.errors.on(:title)
end
# validates_numericality_of :less_than
def test_validates_numericality_of_less_than_generates_message
Topic.validates_numericality_of :title, :only_integer => true, :less_than => 0
@topic.title = 1
@topic.errors.expects(:generate_message).with(:title, :less_than, {:value => 1, :count => 0, :default => nil})
@topic.valid?
end
def test_validates_numericality_of_odd_generates_message_with_custom_default_message
Topic.validates_numericality_of :title, :only_integer => true, :less_than => 0, :message => 'custom'
@topic.title = 1
@topic.errors.expects(:generate_message).with(:title, :less_than, {:value => 1, :count => 0, :default => 'custom'})
@topic.valid?
end
def test_validates_numericality_of_less_than_finds_custom_model_key_translation
I18n.backend.add_translations 'en-US', :active_record => {:error_messages => {:custom => {:topic => {:title => {:less_than => 'custom message'}}}}}
I18n.backend.add_translations 'en-US', :active_record => {:error_messages => {:less_than => 'global message'}}
Topic.validates_numericality_of :title, :only_integer => true, :less_than => 0
@topic.title = 1
@topic.valid?
assert_equal 'custom message', @topic.errors.on(:title)
end
def test_validates_numericality_of_less_than_finds_global_default_translation
I18n.backend.add_translations 'en-US', :active_record => {:error_messages => {:less_than => 'global message'}}
Topic.validates_numericality_of :title, :only_integer => true, :less_than => 0
@topic.title = 1
@topic.valid?
assert_equal 'global message', @topic.errors.on(:title)
end
# validates_associated
def test_validates_associated_generates_message
Topic.validates_associated :replies
replied_topic.errors.expects(:generate_message).with(:replies, :invalid, {:value => replied_topic.replies, :default => nil})
replied_topic.valid?
end
def test_validates_associated_generates_message_with_custom_default_message
Topic.validates_associated :replies
replied_topic.errors.expects(:generate_message).with(:replies, :invalid, {:value => replied_topic.replies, :default => nil})
replied_topic.valid?
end
def test_validates_associated_finds_custom_model_key_translation
I18n.backend.add_translations 'en-US', :active_record => {:error_messages => {:custom => {:topic => {:replies => {:invalid => 'custom message'}}}}}
I18n.backend.add_translations 'en-US', :active_record => {:error_messages => {:invalid => 'global message'}}
Topic.validates_associated :replies
replied_topic.valid?
assert_equal 'custom message', replied_topic.errors.on(:replies)
end
def test_validates_associated_finds_global_default_translation
I18n.backend.add_translations 'en-US', :active_record => {:error_messages => {:invalid => 'global message'}}
Topic.validates_associated :replies
replied_topic.valid?
assert_equal 'global message', replied_topic.errors.on(:replies)
end
end

View File

@ -6,10 +6,15 @@ module ActiveSupport #:nodoc:
module Conversions
# Converts the array to a comma-separated sentence where the last element is joined by the connector word. Options:
# * <tt>:connector</tt> - The word used to join the last element in arrays with two or more elements (default: "and")
# * <tt>:skip_last_comma</tt> - Set to true to return "a, b and c" instead of "a, b, and c".
def to_sentence(options = {})
options.assert_valid_keys(:connector, :skip_last_comma)
options.reverse_merge! :connector => 'and', :skip_last_comma => false
# * <tt>:skip_last_comma</tt> - Set to true to return "a, b and c" instead of "a, b, and c".
def to_sentence(options = {})
options.assert_valid_keys(:connector, :skip_last_comma, :locale)
locale = options[:locale]
locale ||= request.locale if respond_to?(:request)
default = :'support.array.sentence_connector'.t(locale)
options.reverse_merge! :connector => default, :skip_last_comma => false
options[:connector] = "#{options[:connector]} " unless options[:connector].nil? || options[:connector].strip == ''
case length
@ -23,6 +28,7 @@ module ActiveSupport #:nodoc:
"#{self[0...-1].join(', ')}#{options[:skip_last_comma] ? '' : ','} #{options[:connector]}#{self[-1]}"
end
end
# Calls <tt>to_param</tt> on all its elements and joins the result with
# slashes. This is used by <tt>url_for</tt> in Action Pack.

View File

@ -23,4 +23,11 @@ begin
gem 'tzinfo', '~> 0.3.9'
rescue Gem::LoadError
$:.unshift "#{File.dirname(__FILE__)}/vendor/tzinfo-0.3.9"
end
begin
gem 'i18n', '~> 0.3.9'
rescue Gem::LoadError
$:.unshift "#{File.dirname(__FILE__)}/vendor/i18n-0.0.1/lib" # TODO
require 'i18n'
end

@ -0,0 +1 @@
Subproject commit 70ab0f3cc5921cc67e09741939a08b2582d707cb