num2words 0.3.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/docs/rails.md ADDED
@@ -0,0 +1,130 @@
1
+ # Rails Integration
2
+
3
+ `num2words` provides optional Rails helpers without adding Rails as a runtime dependency.
4
+
5
+ If Rails is available, `Num2words::Railtie` automatically includes helpers into Action View. Outside Rails, the helper module can be included manually.
6
+
7
+ The integration is covered by specs with a minimal Rails application, but Rails remains a development/test dependency only.
8
+
9
+ ## Helpers
10
+
11
+ Available helpers:
12
+
13
+ - `number_to_words(number, locale: I18n.locale, **options)`
14
+ - `currency_to_words(amount, locale: I18n.locale, **options)`
15
+ - `date_to_words(date, locale: I18n.locale, **options)`
16
+ - `time_to_words(time, locale: I18n.locale, **options)`
17
+ - `datetime_to_words(datetime, locale: I18n.locale, **options)`
18
+
19
+ ## Rails Views
20
+
21
+ ```erb
22
+ <%= number_to_words(123, locale: :ru) %>
23
+ <%= currency_to_words(@invoice.total, locale: :ru, code: :RUB) %>
24
+ <%= date_to_words(Date.current, locale: I18n.locale) %>
25
+ <%= time_to_words(Time.current, locale: I18n.locale) %>
26
+ ```
27
+
28
+ When `locale:` is omitted, helpers use `I18n.locale`.
29
+
30
+ ```erb
31
+ <%= number_to_words(123) %>
32
+ ```
33
+
34
+ ## Controllers
35
+
36
+ Helpers are also exposed through the controller helper context:
37
+
38
+ ```ruby
39
+ class PaymentsController < ApplicationController
40
+ def show
41
+ @amount_in_words = helpers.currency_to_words(@payment.amount, locale: :ru, code: @payment.currency)
42
+ end
43
+ end
44
+ ```
45
+
46
+ ## ActiveModel Validations
47
+
48
+ `num2words` provides a currency validator for Rails and ActiveModel models:
49
+
50
+ ```ruby
51
+ class Payment < ApplicationRecord
52
+ validates :currency, num2words_currency: { locale: :ru }
53
+ end
54
+ ```
55
+
56
+ Valid values are checked against `Num2words.currency_available?(locale, currency)`.
57
+
58
+ ```ruby
59
+ Payment.new(currency: "RUB").valid?
60
+ # => true
61
+
62
+ Payment.new(currency: "XYZ").valid?
63
+ # => false
64
+ ```
65
+
66
+ The locale can also be dynamic:
67
+
68
+ ```ruby
69
+ class Payment < ApplicationRecord
70
+ validates :currency, num2words_currency: { locale: ->(payment) { payment.locale } }
71
+ end
72
+ ```
73
+
74
+ Locale validation is also available:
75
+
76
+ ```ruby
77
+ class Payment < ApplicationRecord
78
+ validates :locale, num2words_locale: true
79
+ end
80
+ ```
81
+
82
+ ```ruby
83
+ Payment.new(locale: "ru").valid?
84
+ # => true
85
+
86
+ Payment.new(locale: "xx").valid?
87
+ # => false
88
+ ```
89
+
90
+ Valid values are checked against `Num2words.available_locales`.
91
+
92
+ ## Metadata API
93
+
94
+ For forms, select inputs and settings screens, use the public metadata API:
95
+
96
+ ```ruby
97
+ Num2words.available_locales
98
+ Num2words.available_currencies(:ru)
99
+ Num2words.currency_available?(:ru, :RUB)
100
+ Num2words.currency_info(:ru, :RUB)
101
+ Num2words.default_currency_info(:ru)
102
+ ```
103
+
104
+ ## Manual Usage
105
+
106
+ The helpers can be used without Rails:
107
+
108
+ ```ruby
109
+ helper = Object.new.extend(Num2words::Rails::Helpers)
110
+
111
+ helper.number_to_words(123, locale: :en)
112
+ # => "one hundred twenty three"
113
+
114
+ helper.currency_to_words("12.50", locale: :ru, code: :USD)
115
+ # => "двенадцать долларов пятьдесят центов"
116
+ ```
117
+
118
+ ## Options
119
+
120
+ Helpers forward all options to `Num2words.to_words` or `Num2words.to_currency`.
121
+
122
+ ```ruby
123
+ number_to_words(0.5, locale: :ru, joiner: :and)
124
+ # => "ноль и пять десятых"
125
+
126
+ currency_to_words(12, locale: :ru, minor: :nonzero)
127
+ # => "двенадцать рублей"
128
+ ```
129
+
130
+ See [API reference](api.md) for the full option list.
data/docs/rails.ru.md ADDED
@@ -0,0 +1,130 @@
1
+ # Rails-интеграция
2
+
3
+ `num2words` предоставляет optional Rails helpers без добавления Rails как runtime-зависимости.
4
+
5
+ Если Rails доступен, `Num2words::Railtie` автоматически подключает helpers в Action View. Вне Rails модуль helpers можно подключить вручную.
6
+
7
+ Интеграция покрыта specs с минимальным Rails-приложением, но Rails остается зависимостью только для development/test.
8
+
9
+ ## Helpers
10
+
11
+ Доступные helpers:
12
+
13
+ - `number_to_words(number, locale: I18n.locale, **options)`
14
+ - `currency_to_words(amount, locale: I18n.locale, **options)`
15
+ - `date_to_words(date, locale: I18n.locale, **options)`
16
+ - `time_to_words(time, locale: I18n.locale, **options)`
17
+ - `datetime_to_words(datetime, locale: I18n.locale, **options)`
18
+
19
+ ## Rails views
20
+
21
+ ```erb
22
+ <%= number_to_words(123, locale: :ru) %>
23
+ <%= currency_to_words(@invoice.total, locale: :ru, code: :RUB) %>
24
+ <%= date_to_words(Date.current, locale: I18n.locale) %>
25
+ <%= time_to_words(Time.current, locale: I18n.locale) %>
26
+ ```
27
+
28
+ Если `locale:` не передан, helpers используют `I18n.locale`.
29
+
30
+ ```erb
31
+ <%= number_to_words(123) %>
32
+ ```
33
+
34
+ ## Controllers
35
+
36
+ Helpers также доступны через controller helper context:
37
+
38
+ ```ruby
39
+ class PaymentsController < ApplicationController
40
+ def show
41
+ @amount_in_words = helpers.currency_to_words(@payment.amount, locale: :ru, code: @payment.currency)
42
+ end
43
+ end
44
+ ```
45
+
46
+ ## ActiveModel validations
47
+
48
+ `num2words` предоставляет валидатор валюты для Rails и ActiveModel моделей:
49
+
50
+ ```ruby
51
+ class Payment < ApplicationRecord
52
+ validates :currency, num2words_currency: { locale: :ru }
53
+ end
54
+ ```
55
+
56
+ Валидные значения проверяются через `Num2words.currency_available?(locale, currency)`.
57
+
58
+ ```ruby
59
+ Payment.new(currency: "RUB").valid?
60
+ # => true
61
+
62
+ Payment.new(currency: "XYZ").valid?
63
+ # => false
64
+ ```
65
+
66
+ Локаль может быть динамической:
67
+
68
+ ```ruby
69
+ class Payment < ApplicationRecord
70
+ validates :currency, num2words_currency: { locale: ->(payment) { payment.locale } }
71
+ end
72
+ ```
73
+
74
+ Также доступна проверка локали:
75
+
76
+ ```ruby
77
+ class Payment < ApplicationRecord
78
+ validates :locale, num2words_locale: true
79
+ end
80
+ ```
81
+
82
+ ```ruby
83
+ Payment.new(locale: "ru").valid?
84
+ # => true
85
+
86
+ Payment.new(locale: "xx").valid?
87
+ # => false
88
+ ```
89
+
90
+ Валидные значения проверяются через `Num2words.available_locales`.
91
+
92
+ ## Metadata API
93
+
94
+ Для форм, select-полей и настроек можно использовать публичные методы metadata API:
95
+
96
+ ```ruby
97
+ Num2words.available_locales
98
+ Num2words.available_currencies(:ru)
99
+ Num2words.currency_available?(:ru, :RUB)
100
+ Num2words.currency_info(:ru, :RUB)
101
+ Num2words.default_currency_info(:ru)
102
+ ```
103
+
104
+ ## Ручное использование
105
+
106
+ Helpers можно использовать без Rails:
107
+
108
+ ```ruby
109
+ helper = Object.new.extend(Num2words::Rails::Helpers)
110
+
111
+ helper.number_to_words(123, locale: :en)
112
+ # => "one hundred twenty three"
113
+
114
+ helper.currency_to_words("12.50", locale: :ru, code: :USD)
115
+ # => "двенадцать долларов пятьдесят центов"
116
+ ```
117
+
118
+ ## Опции
119
+
120
+ Helpers передают все опции в `Num2words.to_words` или `Num2words.to_currency`.
121
+
122
+ ```ruby
123
+ number_to_words(0.5, locale: :ru, joiner: :and)
124
+ # => "ноль и пять десятых"
125
+
126
+ currency_to_words(12, locale: :ru, minor: :nonzero)
127
+ # => "двенадцать рублей"
128
+ ```
129
+
130
+ Полный список опций смотри в [справочнике API](api.ru.md).
@@ -27,6 +27,34 @@ module Num2words
27
27
  I18n.t("num2words.currencies", locale: locale).keys.map(&:to_sym)
28
28
  end
29
29
 
30
+ def currency_available?(locale, currency)
31
+ available_currencies(locale).include?(currency.to_s.upcase.to_sym)
32
+ end
33
+
34
+ def currency_info(locale, currency)
35
+ code = currency.to_s.upcase.to_sym
36
+ data = I18n.t("num2words.currencies", locale: locale)[code]
37
+
38
+ return unless data
39
+
40
+ {
41
+ code: code,
42
+ major_unit: data[:major_unit],
43
+ minor_unit: data[:minor_unit],
44
+ symbol: data[:symbol]
45
+ }
46
+ end
47
+
48
+ def default_currency_info(locale = I18n.locale)
49
+ currency_info(locale, default_currency(locale))
50
+ end
51
+
52
+ def available_locales
53
+ @available_locales ||= Dir[File.expand_path("../../config/locales/*.yml", __dir__)]
54
+ .map { |file| File.basename(file, ".yml").to_sym }
55
+ .sort
56
+ end
57
+
30
58
  def reset!(locale = nil)
31
59
  return @local_currency.delete(locale) if locale
32
60
 
@@ -52,6 +80,22 @@ module Num2words
52
80
  config.available_currencies(locale)
53
81
  end
54
82
 
83
+ def self.currency_available?(locale, currency)
84
+ config.currency_available?(locale, currency)
85
+ end
86
+
87
+ def self.currency_info(locale, currency)
88
+ config.currency_info(locale, currency)
89
+ end
90
+
91
+ def self.default_currency_info(locale = I18n.locale)
92
+ config.default_currency_info(locale)
93
+ end
94
+
95
+ def self.available_locales
96
+ config.available_locales
97
+ end
98
+
55
99
  def self.currency_warnings
56
100
  config.currency_warnings
57
101
  end
@@ -383,7 +383,8 @@ module Num2words
383
383
  time_format = short && only == :time ? :short : :default
384
384
 
385
385
  date_part = to_words_date(datetime.to_date, locale, locale_data, format: date_format, date_case: date_case)
386
- time_part = to_words_time(datetime.to_time, locale, locale_data, format: time_format, short: short)
386
+ time_part = to_words_time(Time.new(datetime.year, datetime.month, datetime.day, datetime.hour, datetime.min, datetime.sec),
387
+ locale, locale_data, format: time_format, short: short)
387
388
 
388
389
  return date_part if only == :date
389
390
  return time_part if only == :time
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Num2words
4
+ module Rails
5
+ module Helpers
6
+ def number_to_words(number, locale: I18n.locale, **options)
7
+ Num2words.to_words(number, locale: locale, **options)
8
+ end
9
+
10
+ def currency_to_words(amount, locale: I18n.locale, **options)
11
+ Num2words.to_currency(amount, locale: locale, **options)
12
+ end
13
+
14
+ def date_to_words(date, locale: I18n.locale, **options)
15
+ Num2words.to_words(date, locale: locale, **options)
16
+ end
17
+
18
+ def time_to_words(time, locale: I18n.locale, **options)
19
+ Num2words.to_words(time, locale: locale, **options)
20
+ end
21
+
22
+ def datetime_to_words(datetime, locale: I18n.locale, **options)
23
+ Num2words.to_words(datetime, locale: locale, **options)
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_model"
4
+
5
+ module Num2words
6
+ module Rails
7
+ module Validators
8
+ class CurrencyValidator < ActiveModel::EachValidator
9
+ def validate_each(record, attribute, value)
10
+ locale = resolve_locale(record)
11
+ currency = value.to_s.upcase.to_sym
12
+
13
+ return if Num2words.currency_available?(locale, currency)
14
+
15
+ record.errors.add(attribute, :unsupported_currency, currency: currency, locale: locale)
16
+ end
17
+
18
+ private
19
+
20
+ def resolve_locale(record)
21
+ locale = options[:locale]
22
+ locale.respond_to?(:call) ? locale.call(record) : locale || I18n.locale
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
28
+
29
+ class Num2wordsCurrencyValidator < Num2words::Rails::Validators::CurrencyValidator
30
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_model"
4
+
5
+ module Num2words
6
+ module Rails
7
+ module Validators
8
+ class LocaleValidator < ActiveModel::EachValidator
9
+ def validate_each(record, attribute, value)
10
+ locale = value.to_s.to_sym
11
+
12
+ return if Num2words.available_locales.include?(locale)
13
+
14
+ record.errors.add(attribute, :unsupported_locale, locale: locale)
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
20
+
21
+ class Num2wordsLocaleValidator < Num2words::Rails::Validators::LocaleValidator
22
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "rails/helpers"
4
+
5
+ begin
6
+ require "rails/railtie"
7
+ require_relative "rails/validators/currency_validator"
8
+ require_relative "rails/validators/locale_validator"
9
+ rescue LoadError
10
+ nil
11
+ end
12
+
13
+ if defined?(::Rails::Railtie)
14
+ module Num2words
15
+ class Railtie < ::Rails::Railtie
16
+ initializer "num2words.helpers" do
17
+ ActiveSupport.on_load(:action_view) do
18
+ include Num2words::Rails::Helpers
19
+ end
20
+
21
+ ActiveSupport.on_load(:action_controller) do
22
+ helper Num2words::Rails::Helpers if respond_to?(:helper)
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Num2words
4
- VERSION = "0.3.0"
4
+ VERSION = "0.4.0"
5
5
  end
data/lib/num2words.rb CHANGED
@@ -6,6 +6,8 @@ require_relative "num2words/converter"
6
6
  require_relative "num2words/core_ext"
7
7
  require_relative "num2words/locales"
8
8
  require_relative "num2words/config"
9
+ require_relative "num2words/rails/helpers"
10
+ require_relative "num2words/railtie"
9
11
 
10
12
  module Num2words
11
13
  def self.to_words(number, *args, **opts)
data/num2words.gemspec CHANGED
@@ -26,4 +26,5 @@ Gem::Specification.new do |spec|
26
26
  spec.require_paths = ["lib"]
27
27
 
28
28
  spec.add_runtime_dependency "i18n", "~> 1.8"
29
+ spec.add_runtime_dependency "bigdecimal", "~> 3.1"
29
30
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: num2words
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.0
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ruslan Fedotov
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2026-06-27 00:00:00.000000000 Z
11
+ date: 2026-07-05 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: i18n
@@ -24,6 +24,20 @@ dependencies:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
26
  version: '1.8'
27
+ - !ruby/object:Gem::Dependency
28
+ name: bigdecimal
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '3.1'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '3.1'
27
41
  description: Converts numbers, currency amounts, dates, and times to words with locale-aware
28
42
  grammar across supported locales.
29
43
  email:
@@ -33,12 +47,14 @@ executables:
33
47
  extensions: []
34
48
  extra_rdoc_files: []
35
49
  files:
50
+ - ".github/workflows/ci.yml"
36
51
  - ".gitignore"
37
52
  - CHANGELOG.md
38
53
  - Gemfile
39
54
  - LICENSE.txt
40
55
  - README.md
41
56
  - Rakefile
57
+ - benchmark/converter_benchmark.rb
42
58
  - bin/console
43
59
  - bin/setup
44
60
  - config/locales/ar.yml
@@ -91,6 +107,14 @@ files:
91
107
  - config/locales/ur.yml
92
108
  - config/locales/vi.yml
93
109
  - config/locales/zh.yml
110
+ - docs/api.md
111
+ - docs/api.ru.md
112
+ - docs/limits.md
113
+ - docs/limits.ru.md
114
+ - docs/locale_development.md
115
+ - docs/locale_development.ru.md
116
+ - docs/rails.md
117
+ - docs/rails.ru.md
94
118
  - exe/num2words-console
95
119
  - lib/num2words.rb
96
120
  - lib/num2words/config.rb
@@ -149,6 +173,10 @@ files:
149
173
  - lib/num2words/locales/ur.rb
150
174
  - lib/num2words/locales/vi.rb
151
175
  - lib/num2words/locales/zh.rb
176
+ - lib/num2words/rails/helpers.rb
177
+ - lib/num2words/rails/validators/currency_validator.rb
178
+ - lib/num2words/rails/validators/locale_validator.rb
179
+ - lib/num2words/railtie.rb
152
180
  - lib/num2words/version.rb
153
181
  - num2words.gemspec
154
182
  homepage: https://github.com/skyrusx/num2words