diff --git a/README.md b/README.md index 70cfba1b..0a435cd6 100644 --- a/README.md +++ b/README.md @@ -146,6 +146,19 @@ class to get full time zone support. => Thu, 15 Jun 2006 05:45:00 UTC +00:00 ``` +## Localization + +Chronic supports basic localization. You can either create your own based on +the default english locale or use the +[chronic-l10n](http://github.com/luan/chronic-l10n) gem. You can change the +locale by simply changing the `Chronic.locale` value, such as (given you have +:pt-BR available): + + >> Chronic.locale = :'pt-BR' + >> Chronic.parse("15 de Junho de 2006 as 5:45 da manha") + => Thu, 15 Jun 2006 05:45:00 UTC +00:00 + + ## Limitations Chronic uses Ruby's built in Time class for all time storage and computation. diff --git a/lib/chronic.rb b/lib/chronic.rb index 4281a25d..d4b6c596 100644 --- a/lib/chronic.rb +++ b/lib/chronic.rb @@ -53,7 +53,6 @@ module Chronic VERSION = "0.9.1" class << self - # Returns true when debug mode is enabled. attr_accessor :debug @@ -69,11 +68,33 @@ class << self # # Returns The Time class Chronic uses internally. attr_accessor :time_class + + # Returns the available locales that Chronic can use + attr_accessor :locale_hashes + + # The current locale Chronic is using to parse strings + # + # Examples: + # + # require 'chronic' + # + # Chronic.locale = :'pt-BR' + # Chronic.parse('15 de Junho de 2006 as 5:54 da manha ') + # # => Thu, 15 Jun 2006 05:45:00 UTC +00:00 + # + # Returns the locale name Chronic uses internally + attr_accessor :locale end self.debug = false self.time_class = Time + self.locale = :en + + require 'chronic/locales/en' + self.locale_hashes = { + :en => Chronic::Locales::EN + } # Parses a string containing a natural language date or time. # @@ -84,9 +105,47 @@ class << self # text - The String text to parse. # opts - An optional Hash of configuration options passed to Parser::new. def self.parse(text, options = {}) + # ensure current locale is available + raise ArgumentError, "#{locale} is not an available locale" unless has_locale(locale) + Parser.new(options).parse(text) end + # Adds a locale to the locale hash + # + # name - Symbol locale name + # locale - Hash locale values + def self.add_locale(name, locale) + raise ArgumentError, "Locale shoud be a hash" unless locale.is_a?(Hash) + locale_hashes[name] = locale + end + + # Checks if a locale is available + # + # name - Symbol locale name + # + # Returns true if the locale is available, false if not + def self.has_locale(name) + locale_hashes.include? name + end + + + # Returns the translations for the current locale + def self.translate(keys, loc=nil) + loc ||= locale + node = locale_hashes[loc] + + keys.each do |key| + if node.include? key + node = node[key] + else + return translate(keys, :en) + end + end + + node + end + # Construct a new time object determining possible month overflows # and leap years. # @@ -140,5 +199,4 @@ def self.construct(year, month = 1, day = 1, hour = 0, minute = 0, second = 0) Chronic.time_class.local(year, month, day, hour, minute, second) end - end diff --git a/lib/chronic/grabber.rb b/lib/chronic/grabber.rb index b6ced8fa..ff2b807e 100644 --- a/lib/chronic/grabber.rb +++ b/lib/chronic/grabber.rb @@ -18,16 +18,11 @@ def self.scan(tokens, options) # # Returns a new Grabber object. def self.scan_for_all(token) - scan_for token, self, - { - /last/ => :last, - /this/ => :this, - /next/ => :next - } + scan_for token, self, Chronic.translate([:grabber]) end def to_s 'grabber-' << @type.to_s end end -end \ No newline at end of file +end diff --git a/lib/chronic/locales/en.rb b/lib/chronic/locales/en.rb new file mode 100644 index 00000000..fc5c520a --- /dev/null +++ b/lib/chronic/locales/en.rb @@ -0,0 +1,183 @@ +module Chronic + module Locales + EN = { + :pointer => { + /\bpast\b/ => :past, + /\b(?:future|in)\b/ => :future, + }, + :ordinal_regex => /^(\d*)(st|nd|rd|th)$/, + :numerizer => { + :and => 'and', + :preprocess => [ + [/ +|([^\d])-([^\d])/, '\1 \2'], # will mutilate hyphenated-words but shouldn't matter for date extraction + [/a half/, 'haAlf'] # take the 'a' out so it doesn't turn into a 1, save the half for the end + ], + :fractional => [ + [/(\d+)(?: | and |-)*haAlf/i, proc { ($1.to_f + 0.5).to_s }] + ], + :direct_nums => [ + ['eleven', '11'], + ['twelve', '12'], + ['thirteen', '13'], + ['fourteen', '14'], + ['fifteen', '15'], + ['sixteen', '16'], + ['seventeen', '17'], + ['eighteen', '18'], + ['nineteen', '19'], + ['ninteen', '19'], # Common mis-spelling + ['zero', '0'], + ['one', '1'], + ['two', '2'], + ['three', '3'], + ['four(\W|$)', '4\1'], # The weird regex is so that it matches four but not fourty + ['five', '5'], + ['six(\W|$)', '6\1'], + ['seven(\W|$)', '7\1'], + ['eight(\W|$)', '8\1'], + ['nine(\W|$)', '9\1'], + ['ten', '10'], + ['\ba[\b^$]', '1'] # doesn't make sense for an 'a' at the end to be a 1 + ], + :ordinals => [ + ['first', '1'], + ['third', '3'], + ['fourth', '4'], + ['fifth', '5'], + ['sixth', '6'], + ['seventh', '7'], + ['eighth', '8'], + ['ninth', '9'], + ['tenth', '10'] + ], + :ten_prefixes => [ + ['twenty', 20], + ['thirty', 30], + ['forty', 40], + ['fourty', 40], # Common mis-spelling + ['fifty', 50], + ['sixty', 60], + ['seventy', 70], + ['eighty', 80], + ['ninety', 90] + ], + :big_prefixes => [ + ['hundred', 100], + ['thousand', 1000], + ['million', 1_000_000], + ['billion', 1_000_000_000], + ['trillion', 1_000_000_000_000], + ], + }, + + :repeater => { + :season_names => { + /^springs?$/ => :spring, + /^summers?$/ => :summer, + /^(autumn)|(fall)s?$/ => :autumn, + /^winters?$/ => :winter + }, + :month_names => { + /^jan[:\.]?(uary)?$/ => :january, + /^feb[:\.]?(ruary)?$/ => :february, + /^mar[:\.]?(ch)?$/ => :march, + /^apr[:\.]?(il)?$/ => :april, + /^may$/ => :may, + /^jun[:\.]?e?$/ => :june, + /^jul[:\.]?y?$/ => :july, + /^aug[:\.]?(ust)?$/ => :august, + /^sep[:\.]?(t[:\.]?|tember)?$/ => :september, + /^oct[:\.]?(ober)?$/ => :october, + /^nov[:\.]?(ember)?$/ => :november, + /^dec[:\.]?(ember)?$/ => :december + }, + :day_names => { + /^m[ou]n(day)?$/ => :monday, + /^t(ue|eu|oo|u|)s?(day)?$/ => :tuesday, + /^we(d|dnes|nds|nns)(day)?$/ => :wednesday, + /^th(u|ur|urs|ers)(day)?$/ => :thursday, + /^fr[iy](day)?$/ => :friday, + /^sat(t?[ue]rday)?$/ => :saturday, + /^su[nm](day)?$/ => :sunday + }, + :day_portions => { + /^ams?$/ => :am, + /^pms?$/ => :pm, + /^mornings?$/ => :morning, + /^afternoons?$/ => :afternoon, + /^evenings?$/ => :evening, + /^(night|nite)s?$/ => :night + }, + :units => { + /^years?$/ => :year, + /^seasons?$/ => :season, + /^months?$/ => :month, + /^fortnights?$/ => :fortnight, + /^weeks?$/ => :week, + /^weekends?$/ => :weekend, + /^(week|business)days?$/ => :weekday, + /^days?$/ => :day, + /^hrs?$/ => :hour, + /^hours?$/ => :hour, + /^mins?$/ => :minute, + /^minutes?$/ => :minute, + /^secs?$/ => :second, + /^seconds?$/ => :second + } + }, + + :pre_normalize => { + :preprocess => proc {|str| str}, + :pre_numerize => [ + [/\b([ap])\.m\.?/, '\1m'], + [/\./, ':'], + [/['"]/, ''], + [/,/, ' '], + [/^second /, '2nd '], + [/\bsecond (of|day|month|hour|minute|second)\b/, '2nd \1'] + ], + :pos_numerize => [ + [/\-(\d{2}:?\d{2})\b/, 'tzminus\1'], + [/([\/\-\,\@])/, ' \1 '], + [/(?:^|\s)0(\d+:\d+\s*pm?\b)/, ' \1'], + [/\btoday\b/, 'this day'], + [/\btomm?orr?ow\b/, 'next day'], + [/\byesterday\b/, 'last day'], + [/\bnoon\b/, '12:00pm'], + [/\bmidnight\b/, '24:00'], + [/\bnow\b/, 'this second'], + ['quarter', '15'], + ['half', '30'], + [/(\d{1,2}) (to|till|prior to|before)\b/, '\1 minutes past'], + [/(\d{1,2}) (after|past)\b/, '\1 minutes future'], + [/\b(?:ago|before(?: now)?)\b/, 'past'], + [/\bthis (?:last|past)\b/, 'last'], + [/\b(?:in|during) the (morning)\b/, '\1'], + [/\b(?:in the|during the|at) (afternoon|evening|night)\b/, '\1'], + [/\btonight\b/, 'this night'], + [/\b\d+:?\d*[ap]\b/,'\0m'], + [/\b(\d{2})(\d{2})(am|pm)\b/, '\1:\2\3'], + [/(\d)([ap]m|oclock)\b/, '\1 \2'], + [/\b(hence|after|from)\b/, 'future'], + [/^\s?an? /i, '1 '], + [/\b(\d{4}):(\d{2}):(\d{2})\b/, '\1 / \2 / \3'], # DTOriginal + [/\b0(\d+):(\d{2}):(\d{2}) ([ap]m)\b/, '\1:\2:\3 \4'] + ] + }, + + :grabber => { + /last/ => :last, + /this/ => :this, + /next/ => :next + }, + + :token => { + :comma => /^,$/, + :at => /^(at|@)$/, + :in => /^in$/, + :on => /^on$/, + :and => /^and$/ + } + } + end +end diff --git a/lib/chronic/numerizer.rb b/lib/chronic/numerizer.rb index 84d79b79..08bc5cd1 100644 --- a/lib/chronic/numerizer.rb +++ b/lib/chronic/numerizer.rb @@ -3,102 +3,41 @@ module Chronic class Numerizer - DIRECT_NUMS = [ - ['eleven', '11'], - ['twelve', '12'], - ['thirteen', '13'], - ['fourteen', '14'], - ['fifteen', '15'], - ['sixteen', '16'], - ['seventeen', '17'], - ['eighteen', '18'], - ['nineteen', '19'], - ['ninteen', '19'], # Common mis-spelling - ['zero', '0'], - ['one', '1'], - ['two', '2'], - ['three', '3'], - ['four(\W|$)', '4\1'], # The weird regex is so that it matches four but not fourty - ['five', '5'], - ['six(\W|$)', '6\1'], - ['seven(\W|$)', '7\1'], - ['eight(\W|$)', '8\1'], - ['nine(\W|$)', '9\1'], - ['ten', '10'], - ['\ba[\b^$]', '1'] # doesn't make sense for an 'a' at the end to be a 1 - ] - - ORDINALS = [ - ['first', '1'], - ['third', '3'], - ['fourth', '4'], - ['fifth', '5'], - ['sixth', '6'], - ['seventh', '7'], - ['eighth', '8'], - ['ninth', '9'], - ['tenth', '10'], - ['twelfth', '12'], - ['twentieth', '20'], - ['thirtieth', '30'], - ['fourtieth', '40'], - ['fiftieth', '50'], - ['sixtieth', '60'], - ['seventieth', '70'], - ['eightieth', '80'], - ['ninetieth', '90'] - ] - - TEN_PREFIXES = [ - ['twenty', 20], - ['thirty', 30], - ['forty', 40], - ['fourty', 40], # Common mis-spelling - ['fifty', 50], - ['sixty', 60], - ['seventy', 70], - ['eighty', 80], - ['ninety', 90] - ] - - BIG_PREFIXES = [ - ['hundred', 100], - ['thousand', 1000], - ['million', 1_000_000], - ['billion', 1_000_000_000], - ['trillion', 1_000_000_000_000], - ] - def self.numerize(string) string = string.dup # preprocess - string.gsub!(/ +|([^\d])-([^\d])/, '\1 \2') # will mutilate hyphenated-words but shouldn't matter for date extraction - string.gsub!(/a half/, 'haAlf') # take the 'a' out so it doesn't turn into a 1, save the half for the end + Chronic.translate([:numerizer, :preprocess]).each do |pp| + if pp[1].is_a? Proc + string.gsub!(pp[0], &pp[1]) + else + string.gsub!(pp[0], pp[1]) + end + end # easy/direct replacements - DIRECT_NUMS.each do |dn| + Chronic.translate([:numerizer, :direct_nums]).each do |dn| string.gsub!(/#{dn[0]}/i, '' + dn[1]) end - ORDINALS.each do |on| + Chronic.translate([:numerizer, :ordinals]).each do |on| string.gsub!(/#{on[0]}/i, '' + on[1] + on[0][-2, 2]) end # ten, twenty, etc. - TEN_PREFIXES.each do |tp| + Chronic.translate([:numerizer, :ten_prefixes]).each do |tp| string.gsub!(/(?:#{tp[0]}) *(\d(?=[^\d]|$))*/i) { '' + (tp[1] + $1.to_i).to_s } end - TEN_PREFIXES.each do |tp| + Chronic.translate([:numerizer, :ten_prefixes]).each do |tp| string.gsub!(/#{tp[0]}/i) { '' + tp[1].to_s } end # hundreds, thousands, millions, etc. - BIG_PREFIXES.each do |bp| + Chronic.translate([:numerizer, :big_prefixes]).each do |bp| string.gsub!(/(?:)?(\d*) *#{bp[0]}/i) { $1.empty? ? bp[1] : '' + (bp[1] * $1.to_i).to_s} andition(string) end @@ -106,7 +45,13 @@ def self.numerize(string) # fractional addition # I'm not combining this with the previous block as using float addition complicates the strings # (with extraneous .0's and such ) - string.gsub!(/(\d+)(?: | and |-)*haAlf/i) { ($1.to_f + 0.5).to_s } + Chronic.translate([:numerizer, :fractional]).each do |fc| + if fc[1].is_a? Proc + string.gsub!(fc[0], &fc[1]) + else + string.gsub!(fc[0], fc[1]) + end + end string.gsub(//, '') end @@ -117,8 +62,8 @@ class << self def andition(string) sc = StringScanner.new(string) - while sc.scan_until(/(\d+)( | and )(\d+)(?=[^\w]|$)/i) - if sc[2] =~ /and/ || sc[1].size > sc[3].size + while sc.scan_until(/(\d+)( | #{Chronic.translate([:numerizer, :and])} )(\d+)(?=[^\w]|$)/i) + if sc[2] =~ /#{Chronic.translate([:numerizer, :and])}/ || sc[1].size > sc[3].size string[(sc.pos - sc.matched_size)..(sc.pos-1)] = '' + (sc[1].to_i + sc[3].to_i).to_s sc.reset end @@ -127,4 +72,4 @@ def andition(string) end end -end \ No newline at end of file +end diff --git a/lib/chronic/ordinal.rb b/lib/chronic/ordinal.rb index 267ba5b0..44fd2ee2 100644 --- a/lib/chronic/ordinal.rb +++ b/lib/chronic/ordinal.rb @@ -19,14 +19,14 @@ def self.scan(tokens, options) # # Returns a new Ordinal object. def self.scan_for_ordinals(token) - Ordinal.new($1.to_i) if token.word =~ /^(\d*)(st|nd|rd|th)$/ + Ordinal.new($1.to_i) if token.word =~ Chronic.translate([:ordinal_regex]) end # token - The Token object we want to scan. # # Returns a new Ordinal object. def self.scan_for_days(token) - if token.word =~ /^(\d*)(st|nd|rd|th)$/ + if token.word =~ Chronic.translate([:ordinal_regex]) unless $1.to_i > 31 || $1.to_i < 1 OrdinalDay.new(token.word.to_i) end diff --git a/lib/chronic/parser.rb b/lib/chronic/parser.rb index 44ef55f7..535971bc 100644 --- a/lib/chronic/parser.rb +++ b/lib/chronic/parser.rb @@ -86,38 +86,14 @@ def parse(text) # Returns a new String ready for Chronic to parse. def pre_normalize(text) text = text.to_s.downcase - text.gsub!(/\b([ap])\.m\.?/, '\1m') - text.gsub!(/\./, ':') - text.gsub!(/['"]/, '') - text.gsub!(/,/, ' ') - text.gsub!(/^second /, '2nd ') - text.gsub!(/\bsecond (of|day|month|hour|minute|second)\b/, '2nd \1') + text = Chronic.translate([:pre_normalize, :preprocess]).call(text) + Chronic.translate([:pre_normalize, :pre_numerize]).each do |sub| + text.gsub!(*sub) + end text = Numerizer.numerize(text) - text.gsub!(/\-(\d{2}:?\d{2})\b/, 'tzminus\1') - text.gsub!(/([\/\-\,\@])/) { ' ' + $1 + ' ' } - text.gsub!(/(?:^|\s)0(\d+:\d+\s*pm?\b)/, ' \1') - text.gsub!(/\btoday\b/, 'this day') - text.gsub!(/\btomm?orr?ow\b/, 'next day') - text.gsub!(/\byesterday\b/, 'last day') - text.gsub!(/\bnoon\b/, '12:00pm') - text.gsub!(/\bmidnight\b/, '24:00') - text.gsub!(/\bnow\b/, 'this second') - text.gsub!('quarter', '15') - text.gsub!('half', '30') - text.gsub!(/(\d{1,2}) (to|till|prior to|before)\b/, '\1 minutes past') - text.gsub!(/(\d{1,2}) (after|past)\b/, '\1 minutes future') - text.gsub!(/\b(?:ago|before(?: now)?)\b/, 'past') - text.gsub!(/\bthis (?:last|past)\b/, 'last') - text.gsub!(/\b(?:in|during) the (morning)\b/, '\1') - text.gsub!(/\b(?:in the|during the|at) (afternoon|evening|night)\b/, '\1') - text.gsub!(/\btonight\b/, 'this night') - text.gsub!(/\b\d+:?\d*[ap]\b/,'\0m') - text.gsub!(/\b(\d{2})(\d{2})(am|pm)\b/, '\1:\2\3') - text.gsub!(/(\d)([ap]m|oclock)\b/, '\1 \2') - text.gsub!(/\b(hence|after|from)\b/, 'future') - text.gsub!(/^\s?an? /i, '1 ') - text.gsub!(/\b(\d{4}):(\d{2}):(\d{2})\b/, '\1 / \2 / \3') # DTOriginal - text.gsub!(/\b0(\d+):(\d{2}):(\d{2}) ([ap]m)\b/, '\1:\2:\3 \4') + Chronic.translate([:pre_normalize, :pos_numerize]).each do |sub| + text.gsub!(*sub) + end text end diff --git a/lib/chronic/pointer.rb b/lib/chronic/pointer.rb index 50f4abf4..76477593 100644 --- a/lib/chronic/pointer.rb +++ b/lib/chronic/pointer.rb @@ -18,11 +18,7 @@ def self.scan(tokens, options) # # Returns a new Pointer object. def self.scan_for_all(token) - scan_for token, self, - { - /\bpast\b/ => :past, - /\b(?:future|in)\b/ => :future, - } + scan_for token, self, Chronic.translate([:pointer]) end def to_s diff --git a/lib/chronic/repeater.rb b/lib/chronic/repeater.rb index d19f8214..73183795 100644 --- a/lib/chronic/repeater.rb +++ b/lib/chronic/repeater.rb @@ -23,65 +23,28 @@ def self.scan(tokens, options) # # Returns a new Repeater object. def self.scan_for_season_names(token) - scan_for token, RepeaterSeasonName, - { - /^springs?$/ => :spring, - /^summers?$/ => :summer, - /^(autumn)|(fall)s?$/ => :autumn, - /^winters?$/ => :winter - } + scan_for token, RepeaterSeasonName, Chronic.translate([:repeater, :season_names]) end # token - The Token object we want to scan. # # Returns a new Repeater object. def self.scan_for_month_names(token) - scan_for token, RepeaterMonthName, - { - /^jan[:\.]?(uary)?$/ => :january, - /^feb[:\.]?(ruary)?$/ => :february, - /^mar[:\.]?(ch)?$/ => :march, - /^apr[:\.]?(il)?$/ => :april, - /^may$/ => :may, - /^jun[:\.]?e?$/ => :june, - /^jul[:\.]?y?$/ => :july, - /^aug[:\.]?(ust)?$/ => :august, - /^sep[:\.]?(t[:\.]?|tember)?$/ => :september, - /^oct[:\.]?(ober)?$/ => :october, - /^nov[:\.]?(ember)?$/ => :november, - /^dec[:\.]?(ember)?$/ => :december - } + scan_for token, RepeaterMonthName, Chronic.translate([:repeater, :month_names]) end # token - The Token object we want to scan. # # Returns a new Repeater object. def self.scan_for_day_names(token) - scan_for token, RepeaterDayName, - { - /^m[ou]n(day)?$/ => :monday, - /^t(ue|eu|oo|u|)s?(day)?$/ => :tuesday, - /^we(d|dnes|nds|nns)(day)?$/ => :wednesday, - /^th(u|ur|urs|ers)(day)?$/ => :thursday, - /^fr[iy](day)?$/ => :friday, - /^sat(t?[ue]rday)?$/ => :saturday, - /^su[nm](day)?$/ => :sunday - } + scan_for token, RepeaterDayName, Chronic.translate([:repeater, :day_names]) end # token - The Token object we want to scan. # # Returns a new Repeater object. def self.scan_for_day_portions(token) - scan_for token, RepeaterDayPortion, - { - /^ams?$/ => :am, - /^pms?$/ => :pm, - /^mornings?$/ => :morning, - /^afternoons?$/ => :afternoon, - /^evenings?$/ => :evening, - /^(night|nite)s?$/ => :night - } + scan_for token, RepeaterDayPortion, Chronic.translate([:repeater, :day_portions]) end # token - The Token object we want to scan. @@ -95,22 +58,7 @@ def self.scan_for_times(token) # # Returns a new Repeater object. def self.scan_for_units(token) - { - /^years?$/ => :year, - /^seasons?$/ => :season, - /^months?$/ => :month, - /^fortnights?$/ => :fortnight, - /^weeks?$/ => :week, - /^weekends?$/ => :weekend, - /^(week|business)days?$/ => :weekday, - /^days?$/ => :day, - /^hrs?$/ => :hour, - /^hours?$/ => :hour, - /^mins?$/ => :minute, - /^minutes?$/ => :minute, - /^secs?$/ => :second, - /^seconds?$/ => :second - }.each do |item, symbol| + Chronic.translate([:repeater, :units]).each do |item, symbol| if item =~ token.word klass_name = 'Repeater' + symbol.to_s.capitalize klass = Chronic.const_get(klass_name) diff --git a/lib/chronic/scalar.rb b/lib/chronic/scalar.rb index 4f50d421..306ffa04 100644 --- a/lib/chronic/scalar.rb +++ b/lib/chronic/scalar.rb @@ -113,4 +113,4 @@ def to_s super << '-year-' << @type.to_s end end -end \ No newline at end of file +end diff --git a/lib/chronic/separator.rb b/lib/chronic/separator.rb index 64bb8187..c72ecadf 100644 --- a/lib/chronic/separator.rb +++ b/lib/chronic/separator.rb @@ -23,7 +23,7 @@ def self.scan(tokens, options) # # Returns a new SeparatorComma object. def self.scan_for_commas(token) - scan_for token, SeparatorComma, { /^,$/ => :comma } + scan_for token, SeparatorComma, { Chronic.translate([:token, :comma]) => :comma } end # token - The Token object we want to scan. @@ -41,28 +41,28 @@ def self.scan_for_slash_or_dash(token) # # Returns a new SeparatorAt object. def self.scan_for_at(token) - scan_for token, SeparatorAt, { /^(at|@)$/ => :at } + scan_for token, SeparatorAt, { Chronic.translate([:token, :at]) => :at } end # token - The Token object we want to scan. # # Returns a new SeparatorIn object. def self.scan_for_in(token) - scan_for token, SeparatorIn, { /^in$/ => :in } + scan_for token, SeparatorIn, { Chronic.translate([:token, :in]) => :in } end # token - The Token object we want to scan. # # Returns a new SeparatorOn object. def self.scan_for_on(token) - scan_for token, SeparatorOn, { /^on$/ => :on } + scan_for token, SeparatorOn, { Chronic.translate([:token, :on]) => :on } end # token - The Token object we want to scan. # # Returns a new SeperatorAnd Object object. def self.scan_for_and(token) - scan_for token, SeparatorAnd, { /^and$/ => :and } + scan_for token, SeparatorAnd, { Chronic.translate([:token, :and]) => :and } end def to_s diff --git a/test/test_localization.rb b/test/test_localization.rb new file mode 100644 index 00000000..dd59837e --- /dev/null +++ b/test/test_localization.rb @@ -0,0 +1,44 @@ +require 'helper' + +class TestLocalization < TestCase + + def setup + @locale_before = Chronic.locale + @hashes_before = Chronic.locale_hashes + end + + def teardown + Chronic.locale = @locale_before + Chronic.locale_hashes = @hashes_before + end + + def test_default_locale_is_english + assert_equal :en, Chronic.locale + end + + def test_nonexistent_locale + assert_raises(ArgumentError) do + Chronic.locale = :nonexistent + Chronic.parse('some string') + end + end + + def test_add_locale + assert !Chronic.has_locale(:other), ':other locale should NOT be available' + Chronic.locale = :other + other = {} + Chronic.add_locale :other, other + assert Chronic.has_locale(:other), ':other locale should be available' + end + + def test_loads_locale + assert_includes Chronic.translate([:numerizer, :direct_nums]), ['eleven', '11'] + end + + def test_fallsback_if_translation_not_found + Chronic.locale = :not_found + other = {} + Chronic.add_locale :not_found, other + assert_includes Chronic.translate([:numerizer, :direct_nums]), ['eleven', '11'] + end +end