module RuPropisju

Самый лучший, прекрасный, кривой и неотразимый суперпечататель суммы прописью для Ruby.

RuPropisju.rublej(123) # "сто двадцать три рубля"

Constants

CURRENCIES

www.xe.com/symbols.php (лица, приближенные форексам и всяким там валютам и курсам) говорят, что код валюты российского рубля - rub

DECIMALS
MONEY_GENDERS
SUPPORTED_CURRENCIES
TRANSLATIONS
VERSION

Public Class Methods

compose_ordinal(remaining_amount_or_nil, gender, item_forms = [], locale = :ru) click to toggle source

Cоставляет число прописью для чисел до тысячи

# File lib/ru_propisju.rb, line 602
def compose_ordinal(remaining_amount_or_nil, gender, item_forms = [], locale = :ru)

  remaining_amount = remaining_amount_or_nil.to_i

  locale = locale.to_s

  # Ноль чего-то
  # return "ноль %s" % item_forms[3] if remaining_amount_or_nil.zero?

  rest, rest1, chosen_ordinal, ones, tens, hundreds = [nil]*6

  rest = remaining_amount  % 1000
  remaining_amount = remaining_amount / 1000
  if rest.zero?
    # последние три знака нулевые
    return item_forms[2]
  end

  locale_root = pick_locale(TRANSLATIONS, locale)

  # начинаем подсчет с Rest
  # сотни
  hundreds = locale_root[(rest / 100).to_i * 100]

  # десятки
  rest = rest % 100
  rest1 = rest / 10

  # единички
  ones = ""
  tens = locale_root[rest1 == 1 ? rest : rest1 * 10]

  # индекс выбранной формы
  chosen_ordinal = 2
  if rest1 < 1 || rest1 > 1 # единицы
    value = locale_root[rest % 10]
    # если попался хэш, делаем выбор согласно рода
    value = value[gender] if value.kind_of? Hash
    ones = value
    case rest % 10
      when 1 then chosen_ordinal = 0 # индекс формы меняется
      when 2..4 then chosen_ordinal = 1 # индекс формы меняется
    end
  end
  plural = [
    hundreds,
    tens,
    ones,
    item_forms[chosen_ordinal],
  ].compact.reject(&:empty?).join(' ').strip

  return plural
end
delimited_number(number, delimiter) click to toggle source
# File lib/ru_propisju.rb, line 816
def delimited_number(number, delimiter)
  return number.to_s if delimiter == ''

  number.to_s.gsub!(/(\d)(?=(\d\d\d)+(?!\d))/) do |digit_to_delimit|
    "#{digit_to_delimit}#{delimiter}"
  end || number.to_s
end
format_integral(number, options) click to toggle source
# File lib/ru_propisju.rb, line 811
def format_integral(number, options)
  formatted_number = format(options[:integrals_formatter], number)
  delimited_number(formatted_number, options[:integrals_delimiter])
end
pick_locale(from_hash, locale) click to toggle source
# File lib/ru_propisju.rb, line 824
def pick_locale(from_hash, locale)
  return from_hash[locale.to_s] if from_hash.has_key?(locale.to_s)
  raise UnknownLocale, "Unknown locale #{locale.inspect}"
end
propisju_float(num, locale = :ru) click to toggle source

Выдает сумму прописью с учетом дробной доли. Дробная доля округляется до миллионной, или (если дробная доля оканчивается на нули) до ближайшей доли или точки ( 500 тысячных округляется до 5 десятых, 30.0000 до 30).

# File lib/ru_propisju.rb, line 736
def propisju_float(num, locale = :ru)
  locale_root = pick_locale(DECIMALS, locale)
  source_expression = locale_root[:prefix][0]
  target_prefix = locale_root[:prefix][1]
  words = locale_root[:source_words].map do |e|
    [
      e,
      e.gsub(/#{source_expression}$/, target_prefix),
      e.gsub(/#{source_expression}$/, target_prefix),
    ]
  end.freeze

  # Укорачиваем до триллионной доли
  formatted = num.to_s[/^\d+(\.\d{0,#{words.length}})?/].gsub(/0+$/, '')
  wholes, decimals = formatted.split(".")

  return propisju_int(wholes.to_i, 1, [], locale) if decimals.to_i.zero?

  whole_st = propisju_shtuk(wholes.to_i, 2, words[0], locale)

  rem_st = propisju_shtuk(decimals.to_i, 2, words[decimals.length], locale)
  [whole_st, rem_st].compact.join(" ")
end
propisju_int(amount, gender = 1, item_forms = [], locale = :ru) click to toggle source

Выполняет преобразование числа из цифрого вида в символьное

amount - числительное
gender   = 1 - мужской, = 2 - женский, = 3 - средний
one_item - именительный падеж единственного числа (= 1)
two_items - родительный падеж единственного числа (= 2-4)
five_items - родительный падеж множественного числа ( = 5-10)

Примерно так:

propisju(42, 1, ["сволочь", "сволочи", "сволочей"]) # => "сорок две сволочи"
# File lib/ru_propisju.rb, line 770
def propisju_int(amount, gender = 1, item_forms = [], locale = :ru)

  locale_root = pick_locale(TRANSLATIONS, locale)

  # zero!
  if amount.zero?
    return [locale_root['0'], item_forms[-1]].compact.join(' ')
  end

  fractions = [
    [:trillions, 1_000_000_000_000],
    [:billions, 1_000_000_000],
    [:millions, 1_000_000],
    [:thousands, 1_000],
  ]

  parts = fractions.map do | name, multiplier |
    [name, fraction = (amount / multiplier) % 1000]
  end

  if amount / fractions.first.last >= 1000
    raise "Amount is too large" 
  end

  # Единицы обрабатываем отдельно
  ones = amount % 1000

  # Составляем простые тысячные доли
  parts_in_writing = parts.reject do | part |
    part[1].zero?
  end.map do | name, fraction |
    thousandth_gender = (name == :thousands) ? 2 : 1
    compose_ordinal(fraction, thousandth_gender, locale_root[name], locale)
  end

  # И только единицы обрабатываем с переданными параметрами
  parts_in_writing.push(compose_ordinal(ones, gender, item_forms, locale))

  parts_in_writing.compact.join(' ')
end
zero(locale_data, integrals, fractions, fraction_as_number, integrals_as_number, options) click to toggle source
# File lib/ru_propisju.rb, line 585
def zero(locale_data, integrals, fractions, fraction_as_number, integrals_as_number, options)
  integ = integrals_as_number ? format(options[:integrals_formatter], 0) : locale_data['0']
  frac = fraction_as_number ? format(options[:fraction_formatter], 0) : locale_data['0']
  parts = [integ , integrals[-1], frac, fractions[-1]]
  parts.join(' ')
end
zero_fraction(locale, money_gender, fractions, fraction_as_number, options) click to toggle source
# File lib/ru_propisju.rb, line 592
def zero_fraction(locale, money_gender, fractions, fraction_as_number, options)
  if fraction_as_number
    [format(options[:fraction_formatter], 0), choose_plural(0, fractions)]
  else
    propisju_int(0, money_gender, fractions, locale)
  end
end

Public Instance Methods

amount_in_words(amount, currency, locale = :ru, options = {}) click to toggle source

Выводит сумму прописью в зависимости от выбранной валюты. Поддерживаемые валюты: rur, usd, uah, eur

amount_in_words(345.2, 'rur') #=> "триста сорок пять рублей 20 копеек"

Опции

  • :always_show_fraction - true/false. позволяет принудительно отображать 0 в качестве дробной части для целого числа

  • :fraction_formatter - строка. формат отображения числа после точки, например '%d'

# File lib/ru_propisju.rb, line 379
def amount_in_words(amount, currency, locale = :ru, options = {})
  currency = currency.to_s.downcase
  unless CURRENCIES.has_key? currency
    raise UnknownCurrency, "Unsupported currency #{currency}, the following are supported: #{SUPPORTED_CURRENCIES}"
  end
  method(CURRENCIES[currency]).call(amount, locale, options)
end
choose_plural(amount, *variants) click to toggle source

Выбирает нужный падеж существительного в зависимости от числа

choose_plural(3, "штука", "штуки", "штук") #=> "штуки"
# File lib/ru_propisju.rb, line 354
def choose_plural(amount, *variants)
  variants = variants.flatten

  mod_ten = amount % 10
  mod_hundred = amount % 100
  variant = if (mod_ten == 1 && mod_hundred != 11)
      1
  else
    if mod_ten >= 2 && mod_ten <= 4 && (mod_hundred <10 || mod_hundred % 100>=20)
      2
    else
      3
    end
  end
  variants[variant-1]
end
digit_rublej(amount, locale = :ru, options = {}) click to toggle source

Выводит целое или дробное число как сумму в рублях и копейках не прописью

digit_rublej(345.2) #=> "345 рублей 20 копеек"

Опции

  • :always_show_fraction - true/false. позволяет принудительно отображать 0 в качестве дробной части для целого числа

  • :fraction_formatter - строка. формат отображения числа после точки, например '%d'

  • :integrals_formatter - строка. формат отображения целого числа, например '%d'

  • :integrals_delimiter - строка. разделитель разрядов для целой части числа, например ' '

# File lib/ru_propisju.rb, line 424
def digit_rublej(amount, locale = :ru, options = {})
  integrals_key = :rub_integral
  fractions_key = :rub_fraction
  money_gender = MONEY_GENDERS[:rub]

  money(amount, locale, integrals_key, fractions_key, money_gender, true, true, options)
end
dollarov(amount, locale = :ru, options = {}) click to toggle source

Выводит целое или дробное число как сумму в долларах прописью

dollarov(32) #=> "тридцать два доллара"

Опции

  • :always_show_fraction - true/false. позволяет принудительно отображать 0 в качестве дробной части для целого числа

# File lib/ru_propisju.rb, line 463
def dollarov(amount, locale = :ru, options = {})
  integrals_key = :usd_integral
  fractions_key = :usd_fraction
  money_gender = MONEY_GENDERS[:usd]

  money(amount, locale, integrals_key, fractions_key, money_gender, false, false, options)
end
Also aliased as: dollar, dollary
evro(amount, locale = :ru, options = {}) click to toggle source

Выводит целое или дробное число как сумму в евро прописью

evro(32) #=> "тридцать два евро"

Опции

  • :always_show_fraction - true/false. позволяет принудительно отображать 0 в качестве дробной части для целого числа

# File lib/ru_propisju.rb, line 477
def evro(amount, locale = :ru, options = {})
  integrals_key = :eur_integral
  fractions_key = :eur_fraction
  money_gender = MONEY_GENDERS[:eur]

  money(amount, locale, integrals_key, fractions_key, money_gender, false, false, options)
end
griven(amount, locale = :ru, options = {}) click to toggle source

Выводит целое или дробное число как сумму в гривнах прописью

griven(32) #=> "тридцать две гривны"

Опции

  • :always_show_fraction - true/false. позволяет принудительно отображать 0 в качестве дробной части для целого числа

# File lib/ru_propisju.rb, line 449
def griven(amount, locale = :ru, options = {})
  integrals_key = :uah_integral
  fractions_key = :uah_fraction
  money_gender = MONEY_GENDERS[:uah]

  money(amount, locale, integrals_key, fractions_key, money_gender, false, false, options)
end
Also aliased as: grivna, grivny
kopeek(amount, locale = :ru, options = {}) click to toggle source

Выводит сумму прописью в рублях по количеству копеек

kopeek(343) #=> "три рубля 43 копейки"

Опции

  • :always_show_fraction - true/false. позволяет принудительно отображать 0 в качестве дробной части для целого числа

  • :fraction_formatter - строка. формат отображения числа после точки, например '%d'

# File lib/ru_propisju.rb, line 520
def kopeek(amount, locale = :ru, options = {})
  rublej(amount / 100.0, locale, options)
end
Also aliased as: kopeika, kopeiki
money(amount, locale, integrals_key, fractions_key, money_gender, fraction_as_number = false, integrals_as_number = false, options = {}) click to toggle source
# File lib/ru_propisju.rb, line 538
def money(amount, locale, integrals_key, fractions_key, money_gender, fraction_as_number = false, integrals_as_number = false, options = {})

  options[:integrals_formatter] ||= '%d'
  options[:fraction_formatter] ||= '%d'
  options[:integrals_delimiter] ||= ''
  options[:always_show_fraction] ||= false

  locale_data = pick_locale(TRANSLATIONS, locale)
  integrals = locale_data[integrals_key]
  fractions = locale_data[fractions_key]

  return zero(locale_data, integrals, fractions, fraction_as_number, integrals_as_number, options) if amount.zero?

  parts = []

  unless amount.to_i == 0
    if integrals_as_number
      parts << format_integral(amount.to_i, options) << choose_plural(amount.to_i, integrals)
    else
      parts << propisju_int(amount.to_i, money_gender, integrals, locale)
    end
  end

  if amount.kind_of?(Float) || amount.kind_of?(BigDecimal)
    remainder = (amount.divmod(1)[1]*100).round
    if remainder == 100
      parts = [propisju_int(amount.to_i + 1, money_gender, integrals, locale)]
      parts << zero_fraction(locale, money_gender, fractions, fraction_as_number, options) if options[:always_show_fraction]
    else
      if fraction_as_number
        kop = remainder.to_i
        if (!kop.zero? || options[:always_show_fraction])
          parts << format(options[:fraction_formatter], kop) << choose_plural(kop, fractions)
        end
      else
        parts << propisju_int(remainder.to_i, money_gender, fractions, locale)
      end
    end
  else
    parts << zero_fraction(locale, money_gender, fractions, fraction_as_number, options) if options[:always_show_fraction]
  end

  parts.join(' ')
end
propisju(amount, gender, locale = :ru) click to toggle source

Выбирает корректный вариант числительного в зависимости от рода и числа и оформляет сумму прописью

propisju(243, 1) => "двести сорок три"
propisju(221, 2) => "двести двадцать одна"
# File lib/ru_propisju.rb, line 435
def propisju(amount, gender, locale = :ru)
  if amount.kind_of?(Integer)
    propisju_int(amount, gender, [], locale)
  else # также сработает для Decimal, дробные десятичные числительные в долях поэтому женского рода
    propisju_float(amount, locale)
  end
end
propisju_shtuk(items, gender, forms, locale = :ru) click to toggle source

Выводит сумму данного существительного прописью и выбирает правильное число и падеж

RuPropisju.propisju_shtuk(21, 3, "колесо", "колеса", "колес") #=> "двадцать одно колесо"
RuPropisju.propisju_shtuk(21, 1, "мужик", "мужика", "мужиков") #=> "двадцать один мужик"
# File lib/ru_propisju.rb, line 528
def propisju_shtuk(items, gender, forms, locale = :ru)
  elements = if (items == items.to_i)
    [propisju(items, gender, locale), choose_plural(items, forms)]
  else
    [propisju(items, gender, locale), forms[1]]
  end

  elements.join(" ")
end
rublej(amount, locale = :ru, options = {}) click to toggle source

Выводит целое или дробное число как сумму в рублях прописью

rublej(345.2) #=> "триста сорок пять рублей 20 копеек"

Опции

  • :always_show_fraction - true/false. позволяет принудительно отображать 0 в качестве дробной части для целого числа

  • :fraction_formatter - строка. формат отображения числа после точки, например '%d'

# File lib/ru_propisju.rb, line 394
def rublej(amount, locale = :ru, options = {})
  integrals_key = :rub_integral
  fractions_key = :rub_fraction
  money_gender = MONEY_GENDERS[:rub]

  money(amount, locale, integrals_key, fractions_key, money_gender, true, false, options)
end
Also aliased as: rublja, rubl
rublej_extended_format(amount, locale = :ru, options = {}) click to toggle source

Выводит целое или дробное число как сумму в расширенном формате

rublej_extended_format(345.2) #=> "345 рублей 20 копеек (триста сорок пять рублей 20 копеек)"

Опции

  • :always_show_fraction - true/false. позволяет принудительно отображать 0 в качестве дробной части для целого числа

  • :fraction_formatter - строка. формат отображения числа после точки, например '%d'

  • :integrals_formatter - строка. формат отображения целого числа, например '%d'

  • :integrals_delimiter - строка. разделитель разрядов для целой части числа, например ' '

# File lib/ru_propisju.rb, line 411
def rublej_extended_format(amount, locale = :ru, options = {})
  "#{digit_rublej(amount, locale, options)} (#{rublej(amount, locale, options)})"
end
som(amount, locale = :ru, options = {}) click to toggle source

Выводит целое или дробное число как сумму в сомах прописью

som(32) #=> "тридцать два сома"

Опции

  • :always_show_fraction - true/false. позволяет принудительно отображать 0 в качестве дробной части для целого числа

# File lib/ru_propisju.rb, line 505
def som(amount, locale = :ru, options = {})
  integrals_key = :kgs_integral
  fractions_key = :kgs_fraction
  money_gender = MONEY_GENDERS[:kgs]

  money(amount, locale, integrals_key, fractions_key, money_gender, false, false, options)
end
tenge(amount, locale = :ru, options = {}) click to toggle source

Выводит целое или дробное число как сумму в тенге прописью

tenge(32) #=> "тридцать два тенге"

Опции

  • :always_show_fraction - true/false. позволяет принудительно отображать 0 в качестве дробной части для целого числа

# File lib/ru_propisju.rb, line 491
def tenge(amount, locale = :ru, options = {})
  integrals_key = :kzt_integral
  fractions_key = :kzt_fraction
  money_gender = MONEY_GENDERS[:kzt]

  money(amount, locale, integrals_key, fractions_key, money_gender, false, false, options)
end

Private Instance Methods

compose_ordinal(remaining_amount_or_nil, gender, item_forms = [], locale = :ru) click to toggle source

Cоставляет число прописью для чисел до тысячи

# File lib/ru_propisju.rb, line 602
def compose_ordinal(remaining_amount_or_nil, gender, item_forms = [], locale = :ru)

  remaining_amount = remaining_amount_or_nil.to_i

  locale = locale.to_s

  # Ноль чего-то
  # return "ноль %s" % item_forms[3] if remaining_amount_or_nil.zero?

  rest, rest1, chosen_ordinal, ones, tens, hundreds = [nil]*6

  rest = remaining_amount  % 1000
  remaining_amount = remaining_amount / 1000
  if rest.zero?
    # последние три знака нулевые
    return item_forms[2]
  end

  locale_root = pick_locale(TRANSLATIONS, locale)

  # начинаем подсчет с Rest
  # сотни
  hundreds = locale_root[(rest / 100).to_i * 100]

  # десятки
  rest = rest % 100
  rest1 = rest / 10

  # единички
  ones = ""
  tens = locale_root[rest1 == 1 ? rest : rest1 * 10]

  # индекс выбранной формы
  chosen_ordinal = 2
  if rest1 < 1 || rest1 > 1 # единицы
    value = locale_root[rest % 10]
    # если попался хэш, делаем выбор согласно рода
    value = value[gender] if value.kind_of? Hash
    ones = value
    case rest % 10
      when 1 then chosen_ordinal = 0 # индекс формы меняется
      when 2..4 then chosen_ordinal = 1 # индекс формы меняется
    end
  end
  plural = [
    hundreds,
    tens,
    ones,
    item_forms[chosen_ordinal],
  ].compact.reject(&:empty?).join(' ').strip

  return plural
end
delimited_number(number, delimiter) click to toggle source
# File lib/ru_propisju.rb, line 816
def delimited_number(number, delimiter)
  return number.to_s if delimiter == ''

  number.to_s.gsub!(/(\d)(?=(\d\d\d)+(?!\d))/) do |digit_to_delimit|
    "#{digit_to_delimit}#{delimiter}"
  end || number.to_s
end
dollar(amount, locale = :ru, options = {})
Alias for: dollarov
dollary(amount, locale = :ru, options = {})
Alias for: dollarov
format_integral(number, options) click to toggle source
# File lib/ru_propisju.rb, line 811
def format_integral(number, options)
  formatted_number = format(options[:integrals_formatter], number)
  delimited_number(formatted_number, options[:integrals_delimiter])
end
grivna(amount, locale = :ru, options = {})
Alias for: griven
grivny(amount, locale = :ru, options = {})
Alias for: griven
kopeika(amount, locale = :ru, options = {})
Alias for: kopeek
kopeiki(amount, locale = :ru, options = {})
Alias for: kopeek
pick_locale(from_hash, locale) click to toggle source
# File lib/ru_propisju.rb, line 824
def pick_locale(from_hash, locale)
  return from_hash[locale.to_s] if from_hash.has_key?(locale.to_s)
  raise UnknownLocale, "Unknown locale #{locale.inspect}"
end
propisju_float(num, locale = :ru) click to toggle source

Выдает сумму прописью с учетом дробной доли. Дробная доля округляется до миллионной, или (если дробная доля оканчивается на нули) до ближайшей доли или точки ( 500 тысячных округляется до 5 десятых, 30.0000 до 30).

# File lib/ru_propisju.rb, line 736
def propisju_float(num, locale = :ru)
  locale_root = pick_locale(DECIMALS, locale)
  source_expression = locale_root[:prefix][0]
  target_prefix = locale_root[:prefix][1]
  words = locale_root[:source_words].map do |e|
    [
      e,
      e.gsub(/#{source_expression}$/, target_prefix),
      e.gsub(/#{source_expression}$/, target_prefix),
    ]
  end.freeze

  # Укорачиваем до триллионной доли
  formatted = num.to_s[/^\d+(\.\d{0,#{words.length}})?/].gsub(/0+$/, '')
  wholes, decimals = formatted.split(".")

  return propisju_int(wholes.to_i, 1, [], locale) if decimals.to_i.zero?

  whole_st = propisju_shtuk(wholes.to_i, 2, words[0], locale)

  rem_st = propisju_shtuk(decimals.to_i, 2, words[decimals.length], locale)
  [whole_st, rem_st].compact.join(" ")
end
propisju_int(amount, gender = 1, item_forms = [], locale = :ru) click to toggle source

Выполняет преобразование числа из цифрого вида в символьное

amount - числительное
gender   = 1 - мужской, = 2 - женский, = 3 - средний
one_item - именительный падеж единственного числа (= 1)
two_items - родительный падеж единственного числа (= 2-4)
five_items - родительный падеж множественного числа ( = 5-10)

Примерно так:

propisju(42, 1, ["сволочь", "сволочи", "сволочей"]) # => "сорок две сволочи"
# File lib/ru_propisju.rb, line 770
def propisju_int(amount, gender = 1, item_forms = [], locale = :ru)

  locale_root = pick_locale(TRANSLATIONS, locale)

  # zero!
  if amount.zero?
    return [locale_root['0'], item_forms[-1]].compact.join(' ')
  end

  fractions = [
    [:trillions, 1_000_000_000_000],
    [:billions, 1_000_000_000],
    [:millions, 1_000_000],
    [:thousands, 1_000],
  ]

  parts = fractions.map do | name, multiplier |
    [name, fraction = (amount / multiplier) % 1000]
  end

  if amount / fractions.first.last >= 1000
    raise "Amount is too large" 
  end

  # Единицы обрабатываем отдельно
  ones = amount % 1000

  # Составляем простые тысячные доли
  parts_in_writing = parts.reject do | part |
    part[1].zero?
  end.map do | name, fraction |
    thousandth_gender = (name == :thousands) ? 2 : 1
    compose_ordinal(fraction, thousandth_gender, locale_root[name], locale)
  end

  # И только единицы обрабатываем с переданными параметрами
  parts_in_writing.push(compose_ordinal(ones, gender, item_forms, locale))

  parts_in_writing.compact.join(' ')
end
rubl(amount, locale = :ru, options = {})
Alias for: rublej
rublja(amount, locale = :ru, options = {})
Alias for: rublej
zero(locale_data, integrals, fractions, fraction_as_number, integrals_as_number, options) click to toggle source
# File lib/ru_propisju.rb, line 585
def zero(locale_data, integrals, fractions, fraction_as_number, integrals_as_number, options)
  integ = integrals_as_number ? format(options[:integrals_formatter], 0) : locale_data['0']
  frac = fraction_as_number ? format(options[:fraction_formatter], 0) : locale_data['0']
  parts = [integ , integrals[-1], frac, fractions[-1]]
  parts.join(' ')
end
zero_fraction(locale, money_gender, fractions, fraction_as_number, options) click to toggle source
# File lib/ru_propisju.rb, line 592
def zero_fraction(locale, money_gender, fractions, fraction_as_number, options)
  if fraction_as_number
    [format(options[:fraction_formatter], 0), choose_plural(0, fractions)]
  else
    propisju_int(0, money_gender, fractions, locale)
  end
end