Created
June 21, 2013 14:49
-
-
Save eddiefisher/5831695 to your computer and use it in GitHub Desktop.
перевод на русский был портирован отсюда: http://habrahabr.ru/post/53210/
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
module CurrencyInWordsHelper | |
ActionView::Helpers::NumberHelper.class_eval do | |
DEFAULT_CURRENCY_IN_WORDS_VALUES = {:currencies=>{:default=>{:unit=>{:one=>'dollar',:many=>'dollars'}, | |
:decimal=>{:one=>'cent',:many=>'cents'}}}, | |
:connector=>', ',:format=>'%n',:negative_format=>'minus %n'} | |
# Formats a +number+ into a currency string (e.g., 'one hundred dollars'). You can customize the | |
# format in the +options+ hash. | |
# | |
# === Options for all locales | |
# * <tt>:locale</tt> - Sets the locale to be used for formatting (defaults to current locale). | |
# * <tt>:currency</tt> - Sets the denomination of the currency (defaults to :default currency for the locale or "dollar" if not set). | |
# * <tt>:connector</tt> - Sets the connector between integer part and decimal part of the currency (defaults to ", "). | |
# * <tt>:format</tt> - Sets the format for non-negative numbers (defaults to "%n"). | |
# Field is <tt>%n</tt> for the currency amount in words. | |
# * <tt>:negative_format</tt> - Sets the format for negative numbers (defaults to prepending "minus" to the number in words). | |
# Field is <tt>%n</tt> for the currency amount in words (same as format). | |
# | |
# ==== Examples | |
# [<tt>number_to_currency_in_words(123456.50)</tt>] | |
# \=> one hundred and twenty-three thousand four hundred and fifty-six dollars, fifty cents | |
# [<tt>number_to_currency_in_words(123456.50, :connector => ' and ')</tt>] | |
# \=> one hundred and twenty-three thousand four hundred and fifty-six dollars and fifty cents | |
# [<tt>number_to_currency_in_words(123456.50, :locale => :fr, :connector => ' et ')</tt>] | |
# \=> cent vingt-trois mille quatre cent cinquante-six dollars et cinquante cents | |
# [<tt>number_to_currency_in_words(80300.80, :locale => :fr, :currency => :euro, :connector => ' et ')</tt>] | |
# \=> quatre-vingt mille trois cents euros et quatre-vingts centimes | |
# | |
# === Options only available for :en locale | |
# * <tt>:delimiter</tt> - Sets the thousands delimiter (defaults to false). | |
# * <tt>:skip_and</tt> - Skips the 'and' part in number - US (defaults to false). | |
# | |
# ==== Examples | |
# [<tt>number_to_currency_in_words(201201201.201, :delimiter => true)</tt>] | |
# \=> two hundred and one million, two hundred and one thousand, two hundred and one dollars, twenty cents | |
# [<tt>number_to_currency_in_words(201201201.201, :delimiter => true, :skip_and => true)</tt>] | |
# \=> two hundred one million, two hundred one thousand, two hundred one dollars, twenty cents | |
def number_to_currency_in_words number, options = {} | |
options.symbolize_keys! | |
currency_in_words = I18n.translate(:'number.currency_in_words', :locale => options[:locale], :default => {}) | |
defaults = DEFAULT_CURRENCY_IN_WORDS_VALUES.merge(currency_in_words) | |
options = defaults.merge!(options) | |
unless options[:currencies].has_key?(:default) | |
options[:currencies].merge!(DEFAULT_CURRENCY_IN_WORDS_VALUES[:currencies]) | |
end | |
format = options.delete(:format) | |
currency = options.delete(:currency) | |
currencies = options.delete(:currencies) | |
options[:currency] = currency && currencies.has_key?(currency) ? currencies[currency] : currencies[:default] | |
options[:locale] ||= I18n.default_locale | |
if number.to_f < 0 | |
format = options.delete(:negative_format) | |
number = number.respond_to?("abs") ? number.abs : number.sub(/^-/, '') | |
end | |
options_precision = { | |
:precision => 2, | |
:delimiter => '', | |
:significant => false, | |
:strip_insignificant_zeros => false, | |
:separator => '.', | |
:raise => true | |
} | |
begin | |
rounded_number = number_with_precision(number, options_precision) | |
rescue ActionView::Helpers::NumberHelper::InvalidNumberError => e | |
if options[:raise] | |
raise | |
else | |
rounded_number = format.gsub(/%n/, e.number) | |
return e.number.to_s.html_safe? ? rounded_number.html_safe : rounded_number | |
end | |
end | |
begin | |
klass = "CurrencyInWordsHelper::#{options[:locale].to_s.capitalize}Texterizer".constantize | |
rescue NameError | |
if options[:raise] | |
raise NameError, "Implement a class #{options[:locale].to_s.capitalize}Texterizer to support this locale, please." | |
else | |
klass = EnTexterizer | |
end | |
end | |
number_parts = rounded_number.split(options_precision[:separator]).map(&:to_i) | |
texterizer = CurrencyInWordsHelper::Texterizer.new(klass.new, number_parts, options) | |
texterized_number = texterizer.texterize | |
format.gsub(/%n/, texterized_number).html_safe | |
end | |
end | |
#### | |
# :nodoc: all | |
# This is the context class for texterizers | |
class Texterizer | |
attr_reader :number_parts, :options, :texterizer | |
def initialize texterizer, splitted_number, options = {} | |
@texterizer = texterizer | |
@number_parts = splitted_number | |
@options = options | |
end | |
def texterize | |
if @texterizer.respond_to?('texterize') | |
texterized_number = @texterizer.texterize self | |
if texterized_number.is_a?(String) | |
return texterized_number | |
else | |
raise TypeError, "a texterizer must return a String" if @options[:raise] | |
end | |
else | |
raise NoMethodError, "a texterizer must provide a 'texterize' method" if @options[:raise] | |
end | |
# Fallback on EnTexterizer | |
unless @texterizer.instance_of?(EnTexterizer) | |
@texterizer = EnTexterizer.new | |
self.texterize | |
else | |
raise RuntimeError, "you should use the option ':raise => true' to see what goes wrong" | |
end | |
end | |
end | |
#### | |
# :nodoc: all | |
# This is the strategy class for English language | |
class EnTexterizer | |
def texterize context | |
int_part, dec_part = context.number_parts | |
connector = context.options[:connector] | |
int_unit_one = context.options[:currency][:unit][:one] | |
int_unit_many = context.options[:currency][:unit][:many] | |
dec_unit_one = context.options[:currency][:decimal][:one] | |
dec_unit_many = context.options[:currency][:decimal][:many] | |
@skip_and = context.options[:skip_and] || false | |
@delimiter = context.options[:delimiter] || false | |
unless int_unit_many | |
int_unit_many = int_unit_one+'s' | |
end | |
unless dec_unit_many | |
dec_unit_many = dec_unit_one+'s' | |
end | |
int_unit = int_part > 1 ? int_unit_many : int_unit_one | |
dec_unit = dec_part > 1 ? dec_unit_many : dec_unit_one | |
texterized_int_part = (texterize_by_group(int_part).compact << int_unit).flatten.join(' ') | |
texterized_dec_part = (texterize_by_group(dec_part).compact << dec_unit).flatten.join(' ') | |
if dec_part.zero? | |
texterized_int_part | |
else | |
texterized_int_part << connector << texterized_dec_part | |
end | |
end | |
private | |
# :nodoc: all | |
A = %w(zero one two three four five six seven eight nine) | |
B = %w(ten eleven twelve thirteen fourteen fifteen sixteen | |
seventeen eighteen nineteen) | |
C = [nil,nil,'twenty','thirty','forty','fifty','sixty','seventy', | |
'eighty','ninety'] | |
D = [nil,'thousand','million','billion','trillion','quadrillion', | |
'quintillion','sextillion','septillion','octillion'] | |
def texterize_by_group number, group=0 | |
return [under_100(number)] if number.zero? | |
q,r = number.divmod 1000 | |
arr = texterize_by_group(q, group+1) if q > 0 | |
if r.zero? | |
arr.last.chop! if group.zero? && @delimiter && arr.last.respond_to?('chop!') | |
arr | |
else | |
arr = arr.to_a | |
unless group.zero? | |
arr << under_1000(r) | |
arr << D[group] + (',' if @delimiter).to_s | |
else | |
arr.last.chop! if @delimiter && r < 100 && arr.last.respond_to?('chop!') | |
arr << 'and' if !@skip_and && q > 0 && r < 100 | |
arr << under_1000(r) | |
end | |
end | |
end | |
def under_1000 number | |
q,r = number.divmod 100 | |
arr = ([A[q]] << 'hundred' + (' and' unless @skip_and || r.zero?).to_s) if q > 0 | |
r.zero? ? arr : arr.to_a << under_100(r) | |
end | |
def under_100 number | |
case number | |
when 0..9 then A[number] | |
when 10..19 then B[number - 10] | |
else | |
q,r = number.divmod 10 | |
C[q] + ('-' + A[r] unless r.zero?).to_s | |
end | |
end | |
end | |
#### | |
# :nodoc: all | |
# This is the strategy class for Russian language | |
class RuTexterizer | |
def texterize context | |
int_part, dec_part = context.number_parts | |
connector = context.options[:connector] | |
int_unit_one = context.options[:currency][:unit][:one] | |
int_unit_many = context.options[:currency][:unit][:many] | |
int_unit_more = context.options[:currency][:unit][:more] | |
dec_unit_one = context.options[:currency][:decimal][:one] | |
dec_unit_many = context.options[:currency][:decimal][:many] | |
nul = 'ноль' | |
ten = [ | |
['', 'один', 'два', 'три', 'четыре', 'пять', 'шесть', 'семь', 'восемь', 'девять'], | |
['', 'одна', 'две', 'три', 'четыре', 'пять', 'шесть', 'семь', 'восемь', 'девять'] | |
] | |
a20 = %w(десять одиннадцать двенадцать тринадцатьчетырнадцать пятнадцать шестнадцать семнадцать восемнадцать девятнадцать) | |
tens = %w('' '' двадцать тридцать сорок пятьдесят шестьдесят семьдесят восемьдесят девяносто) | |
hundred = %w('' сто двести триста четыреста пятьсот шестьсот семьсот восемьсот девятьсот) #hundred | |
unit = [ | |
['копейка' ,'копейки' ,'копеек', 1], | |
['рубль' ,'рубля' ,'рублей', 0], | |
['тысяча' ,'тысячи' ,'тысяч', 1], | |
['миллион' ,'миллиона','миллионов', 0], | |
['миллиард','милиарда','миллиардов', 0] | |
] | |
out = [] | |
if int_part > 0 | |
int_part.to_s.scan(/(\d{1,3})/).each_with_index do |value, index| | |
key = unit.size-index-3 | |
gender = unit[key][3] | |
ts = value[0].to_s.split('').map{ |i| i.to_i } | |
out << hundred[ts[0]] | |
if ts[1] > 1 | |
out << tens[ts[1]] + ten[gender][ts[2]] | |
else | |
if ts[1] > 0 | |
out << a20[ts[2]] | |
else | |
out << ten[gender][ts[2]] | |
end | |
end | |
if int_part > 1 | |
out << morph(value[0].to_i, unit[key]) | |
end | |
end | |
end | |
out << "#{dec_part} #{morph(dec_part, unit[0])}" | |
out.join(' ') | |
end | |
private | |
def morph(number, array_words) | |
number = number % 100 | |
if number > 10 && number < 20 | |
return array_words[2] | |
end | |
number = number % 10 | |
if number > 1 && number < 5 | |
return array_words[1] | |
end | |
if number == 1 | |
return array_words[0] | |
end | |
return array_words[2] | |
end | |
end | |
#### | |
# :nodoc: all | |
# This is the strategy class for French language | |
class FrTexterizer | |
def texterize context | |
int_part, dec_part = context.number_parts | |
connector = context.options[:connector] | |
int_unit_one = context.options[:currency][:unit][:one] | |
int_unit_many = context.options[:currency][:unit][:many] | |
int_unit_more = context.options[:currency][:unit][:more] | |
dec_unit_one = context.options[:currency][:decimal][:one] | |
dec_unit_many = context.options[:currency][:decimal][:many] | |
unless int_unit_many | |
int_unit_many = int_unit_one+'s' | |
end | |
unless int_unit_more | |
int_unit_more = if int_unit_many.start_with?("a","e","i","o","u") | |
"d'"+int_unit_many | |
else | |
"de "+int_unit_many | |
end | |
end | |
unless dec_unit_many | |
dec_unit_many = dec_unit_one+'s' | |
end | |
int_unit = if int_part > 1 | |
(int_part % 10**6).zero? ? int_unit_more : int_unit_many | |
else | |
int_unit_one | |
end | |
dec_unit = dec_part > 1 ? dec_unit_many : dec_unit_one | |
feminize = context.options[:currency][:unit][:feminine] || false | |
texterized_int_part = (texterize_by_group(int_part, 0, feminize).compact << int_unit).flatten.join(' ') | |
feminize = context.options[:currency][:decimal][:feminine] || false | |
texterized_dec_part = (texterize_by_group(dec_part, 0, feminize).compact << dec_unit).flatten.join(' ') | |
if dec_part.zero? | |
texterized_int_part | |
else | |
texterized_int_part << connector << texterized_dec_part | |
end | |
end | |
private | |
# :nodoc: all | |
A = %w(zéro un deux trois quatre cinq six sept huit neuf) | |
B = %w(dix onze douze treize quatorze quinze seize dix-sept dix-huit dix-neuf) | |
C = [nil,nil,'vingt','trente','quarante','cinquante', | |
'soixante','soixante','quatre-vingt','quatre-vingt'] | |
D = [nil,'mille','million','milliard','billion','billiard','trillion','trilliard', | |
'quadrillion','quadrilliard'] | |
def texterize_by_group number, group, feminine | |
return [under_100(number, 0, feminine)] if number.zero? | |
q,r = number.divmod 1000 | |
arr = texterize_by_group(q, group+1, feminine) if q > 0 | |
if r.zero? | |
arr | |
else | |
arr = arr.to_a | |
arr << under_1000(r, group, feminine) | |
group.zero? ? arr : arr << (D[group] + ('s' if r > 1 && group != 1).to_s) | |
end | |
end | |
def under_1000 number, group, feminine | |
q,r = number.divmod 100 | |
arr = (q > 1 ? [A[q]] : []) << (r == 0 && q > 1 && group != 1 ? 'cents' : 'cent') if q > 0 | |
r.zero? ? arr : (r == 1 && q == 0 && group == 1 ? nil : arr.to_a << under_100(r, group, feminine)) | |
end | |
def under_100 number, group, feminine | |
feminine = (feminine and group.zero?) | |
case number | |
when 0..9 then A[number] + ('e' if feminine && number == 1).to_s | |
when 10..19 then B[number - 10] | |
else | |
q,r = number.divmod 10 | |
case r | |
when 1 | |
case q | |
when 7 then C[q] + ('-et-' + B[r]).to_s | |
when 8 then C[q] + ('-' + A[r]).to_s + ('e' if feminine).to_s | |
when 9 then C[q] + ('-' + B[r]).to_s | |
else C[q] + ('-et-' + A[r]).to_s + ('e' if feminine).to_s | |
end | |
else | |
if [7,9].include?(q) | |
C[q] + ('-' + B[r]).to_s | |
else | |
C[q] + ('-' + A[r] if not r.zero?).to_s + ('s' if number == 80 && group != 1).to_s | |
end | |
end | |
end | |
end | |
end | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
использование:
<%= number_to_currency_in_words @order.total_price, locale: :ru %>