num2words 0.1.4 → 0.1.5

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 1809289bec98157e0b78b8bbe9f18b5fab8fa5f111293105e89b5747d866e43d
4
- data.tar.gz: 1e5cfdd50831c7e9f75a8216488268564cda27b7972c70feca0a9d34c05c907b
3
+ metadata.gz: 7fbb8e0f384b3baf9ca9081c7d2c2095817d883b55796d48196f0bfd5a111fcc
4
+ data.tar.gz: 1dd172a547a50ac707b78cb626e1ae81b0af1a631e7313b30a48afe414dc5d26
5
5
  SHA512:
6
- metadata.gz: 353353c876466e244bf2748073d7a3534a5ead257bc8d7f70f35d975a45762f4d2e0023d2cbf6e55136587974278093987fd2bba4d20ec972f33bedc9a1820c2
7
- data.tar.gz: 8037de95f9896db0f4b00aa16710836de2337416c646dfc40900f3021a019aba9c04ae7f06842ebc6be73904aca9156258ee766992a861196a3274008ca13ab4
6
+ metadata.gz: 73b3f95666700ff9ad74b73c47f207becb76541d185dd0bdee98cbc89b2b33921a7b97651c844bc83029706cc52a84fde1cac98da8151bff8cfa1f77867cd1c2
7
+ data.tar.gz: 9008411287c518ecbc87c16426e9eaa9aebdb4f526a044a84a135df1c698cde43a16841cd9397faa97a890a3d1326df0945437a2b9112b608454e258f41b8f5f
data/CHANGELOG.md CHANGED
@@ -15,6 +15,29 @@
15
15
 
16
16
  ---
17
17
 
18
+ ## [0.1.5] - 2025-08-22
19
+ ### Added
20
+ - Расширение `Time#to_words`, возможность преобразовать строки с временем
21
+ ```ruby
22
+ "14:35:42".to_words(:ru)
23
+ # => "четырнадцать часов тридцать пять минут сорок две секунды"
24
+ ```
25
+ - Добавлен параметр `short: true` для `time` и `datetime`
26
+ Возвращает краткий вариант записи для time (`HH:MM`)
27
+ ```ruby
28
+ "14:35:42".to_words(:ru, short: true)
29
+ # => "четырнадцать часов тридцать пять минут"
30
+ ```
31
+ Возвращает краткий вариант записи для datetime на выбор - дата или время
32
+ ```ruby
33
+ "2024-08-21 14:35:42".to_words(:ru, :time)
34
+ # => "двадцать первого августа две тысячи двадцать четвёртого года"
35
+ ```
36
+ - Поддержка перевода времени в разных форматах:
37
+ - `:hours_only` — только часы
38
+ - `:hours_minutes` — часы и минуты
39
+ - `:hours_minutes_seconds` — часы, минуты и секунды
40
+
18
41
  ## [0.1.4] - 2025-08-21
19
42
  ### Added
20
43
  - Расширение `String#to_words`, возможность преобразовать строки с датами
data/README.md CHANGED
@@ -6,17 +6,20 @@
6
6
 
7
7
  ## ✨ Основные возможности
8
8
 
9
- - Поддержка **современных локалей** (включая русский и английский) через YAML-файлы `config/locales/*.yml`.
10
- - Удобные методы:
11
- - `Num2words.to_words(123)`
12
- - `Num2words.to_currency(12.34)`
13
- - Расширение `Integer` и `Float`:
14
- - `123.to_words(:en)`
15
- - `12.5.to_currency(locale: :ru)`
16
- - Короткая запись локали (символом): `123.to_words(:en)`
17
- - Lazy‑загрузка локалей (`lib/num2words/locales.rb`).
18
- - Унифицированный `Converter`, делегирующий логику нужной локали.
19
- - Разряды чисел и валюты настраиваются через I18n YAML.
9
+ - Поддержка **чисел, времени, дат и валют**:
10
+ - `Integer` → `123.to_words(:ru)` → `"сто двадцать три"`
11
+ - `Float` → `12.5.to_words(:en)` → `"twelve and five tenths"`
12
+ - `Time / DateTime` → `"2024-08-21 14:35:42".to_words(:ru, :time)` → `"четырнадцать часов тридцать пять минут сорок две секунды"`
13
+ - `Float.to_currency` `12.5.to_currency(:ru)` `"двенадцать рублей пятьдесят копеек"`
14
+ - Кастомизация вывода:
15
+ - Краткая форма (`short: true`)`"четырнадцать часов тридцать пять минут"`
16
+ - Форматы времени (`:hours_only`, `:hours_minutes`, `:hours_minutes_seconds`)
17
+ - Выбор локали: `:ru`, `:en` (из коробки), легко расширить YAML-файлами
18
+ - Расширение **Integer**, **Float**, **Date**, **Time**, **DateTime** удобными методами.
19
+ - Локали инициализируются только при обращении к ним (`lazy loading`) через `lib/num2words/locales.rb`.
20
+ - Унифицированный `Converter`, делегирующий работу нужной локали.
21
+ - Настраиваемые правила разрядов чисел и валют через I18n YAML.
22
+
20
23
 
21
24
  ---
22
25
 
@@ -40,21 +43,64 @@ gem install num2words
40
43
 
41
44
  ```ruby
42
45
  require "num2words"
46
+ ```
47
+
48
+ ### 🔢 Числа
49
+ ```ruby
50
+ # Integer
51
+ 123.to_words(:ru) # => "сто двадцать три"
52
+ 123.to_words(:en) # => "one hundred twenty three"
53
+
54
+ # Float
55
+ 45.67.to_words(:ru) # => "сорок пять целых шестьдесят семь сотых"
56
+ 45.67.to_words(:en) # => "forty five and sixty seven hundredths"
57
+ ```
43
58
 
44
- # По умолчанию используется I18n.locale или fallback :ru
45
- Num2words.to_words(21) # => "двадцать один"
46
- Num2words.to_currency(21.05) # => "двадцать один рубль пять копеек"
59
+ ### 💰 Валюта
60
+ ```ruby
61
+ 21.05.to_currency(:ru) # => "двадцать один рубль пять копеек"
62
+ 12.5.to_currency(:en) # => "twelve dollars fifty cents"
63
+ ```
47
64
 
48
- # Short-style:
49
- 123.to_words # "сто двадцать три"
50
- 123.to_words(:en) # "one hundred twenty three"
51
- 123.to_words(locale: :en) # "one hundred twenty three"
65
+ ### 📅 Дата
66
+ ```ruby
67
+ "2024-08-21".to_words(:ru, :date)
68
+ # => "двадцать первое августа две тысячи двадцать четвертого года"
52
69
 
53
- # Валюта:
54
- 12.5.to_currency(:en) # "twelve rubles fifty kopeks"
55
- 12.5.to_currency(locale: :ru) # "двенадцать рублей пятьдесят копеек"
70
+ "2024-08-21".to_words(:en, :date)
71
+ # => "the twenty-first of August, two thousand twenty four"
56
72
  ```
57
73
 
74
+ ### ⏰ Время
75
+ ```ruby
76
+ "14:35:42".to_words(:ru, :time)
77
+ # => "четырнадцать часов тридцать пять минут сорок две секунды"
78
+
79
+ "14:35:42".to_words(:ru, :time, short: true) # или короткая запись: "14:35:42".to_words(:ru, :time, true)
80
+ # => "четырнадцать часов тридцать пять минут"
81
+
82
+ "14:35:42".to_words(:en, :time)
83
+ # => "fourteen hours thirty five minutes forty two seconds"
84
+ ```
85
+
86
+ ### 🕓 Дата и время
87
+
88
+ ```ruby
89
+ "2024-08-21 14:35:42".to_words(:ru)
90
+ # => "двадцать первого августа две тысячи двадцать четвёртого года, четырнадцать часов тридцать пять минут сорок две секунды"
91
+
92
+ "2024-08-21 14:35:42".to_words(:en)
93
+ # => "the twenty-first of August, two thousand twenty four at fourteen hours thirty five minutes forty two seconds"
94
+ ```
95
+
96
+ ### ⚙️ Опции
97
+
98
+ - `locale: :ru | :en` — язык (по умолчанию берётся I18n.locale или :ru).
99
+ - `:date, :time, :datetime` — формат преобразования для строк.
100
+ - `short: true` — краткая форма для даты/времени.
101
+ - `Num2words.to_words(number, locale: :en)` — универсальный способ вызова без расширения базовых классов.
102
+ - `Num2words.to_currency(number)` — преобразование число в валюту.
103
+
58
104
  ### Консоль 💻
59
105
 
60
106
  Num2words поддерживает интерактивную консоль для быстрого тестирования.
@@ -138,6 +184,20 @@ bin/console
138
184
  - YAML-файл: `config/locales/xx.yml`
139
185
  - Обёртка: `lib/num2words/locales/xx.rb`
140
186
 
187
+ ### 🌍 Поддержка локалей (ru / en)
188
+
189
+ | Возможность | ru | en |
190
+ | ----------------------------- | --------- | ---------- |
191
+ | 🔢 Числа (Integer) | ✔ | ✔ |
192
+ | 🔢 Числа (Float) | ✔ | ✔ |
193
+ | 💰 Валюта | ✔ (рубли) | ✔ (rubles) |
194
+ | 💰 Валюта (short) | ✖ | ✖ |
195
+ | 💱 Выбор валюты (USD/EUR/…) | ✖ | ✖ |
196
+ | 📅 Дата | ✔ | ✔ |
197
+ | ⏰ Время | ✔ | ✔ |
198
+ | 🕓 Дата и время | ✔ | ✔ |
199
+ | 📝 Краткая форма даты/времени | ✔ | ✔ |
200
+
141
201
  ---
142
202
 
143
203
  ## 🧪 Тестирование
@@ -149,8 +209,10 @@ bundle exec rspec
149
209
  ## 📌 Roadmap
150
210
 
151
211
  - [x] 🇬🇧 Поддержка английского языка
152
- - [ ] 💵 Поддержка других валют (USD, EUR)
153
- - [ ] 🔠 Опция выбора регистра (строчные/Прописные)
212
+ - [x] 🔠 Опция выбора регистра (строчные/Прописные)
213
+ - [x] Краткая форма даты/времени
214
+ - [ ] 💵 Поддержка других валют (USD, EUR и т.д)
215
+ - [ ] 💰 Краткая форма валюты
154
216
 
155
217
  ---
156
218
 
@@ -40,12 +40,6 @@ en:
40
40
  100000000: ["hundred-millionth", "hundred-millionths", "hundred-millionths"]
41
41
  1000000000: ["billionth", "billionths", "billionths"]
42
42
 
43
- date:
44
- template:
45
- default: "the %{day} of %{month}, %{year}"
46
- months:
47
- default: ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"]
48
-
49
43
  numbers:
50
44
  ordinals:
51
45
  default:
@@ -53,3 +47,19 @@ en:
53
47
  "tenth", "eleventh", "twelfth", "thirteenth", "fourteenth" ,"fifteenth", "sixteenth", "seventeenth", "eighteenth", "nineteenth",
54
48
  "twentieth", "twenty-first", "twenty-second", "twenty-third", "twenty-fourth", "twenty-fifth", "twenty-sixth", "twenty-seventh", "twenty-eighth", "twenty-ninth",
55
49
  "thirtieth","thirty-first"]
50
+ datetime:
51
+ template: "%{date} at %{time}"
52
+ date:
53
+ template:
54
+ default: "the %{day} of %{month}, %{year}"
55
+ months:
56
+ default: [ "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December" ]
57
+ time:
58
+ template:
59
+ hours_only: "%{hours}"
60
+ hours_minutes: "%{hours} %{minutes}"
61
+ hours_minutes_seconds: "%{hours} %{minutes} %{seconds}"
62
+ words:
63
+ hour: ["hour", "hours", "hours"]
64
+ minute: ["minute", "minutes", "minutes"]
65
+ second: ["second", "seconds", "seconds"]
@@ -35,14 +35,6 @@ ru:
35
35
  100000000: ["стомиллионная", "стомиллионные", "стомиллионных"]
36
36
  1000000000: ["миллиардная", "миллиардные", "миллиардных"]
37
37
 
38
- date:
39
- template:
40
- default: "%{day} %{month} %{year} года"
41
- nominative: "%{day} %{month} %{year} год"
42
- months:
43
- default: ["января", "февраля", "марта", "апреля", "мая", "июня", "июля", "августа", "сентября", "октября", "ноября", "декабря"]
44
- nominative: ["января", "февраля", "марта", "апреля", "мая", "июня", "июля", "августа", "сентября", "октября", "ноября", "декабря"]
45
-
46
38
  numbers:
47
39
  ordinals:
48
40
  nominative:
@@ -63,3 +55,22 @@ ru:
63
55
  "десятого", "одиннадцатого", "двенадцатого", "тринадцатого", "четырнадцатого", "пятнадцатого", "шестнадцатого", "семнадцатого", "восемнадцатого", "девятнадцатого",
64
56
  "двадцатого", "двадцать первого", "двадцать второго", "двадцать третьего", "двадцать четвёртого", "двадцать пятого", "двадцать шестого", "двадцать седьмого", "двадцать восьмого", "двадцать девятого",
65
57
  "тридцатого", "тридцать первого"]
58
+
59
+ datetime:
60
+ template: "%{date}, %{time}"
61
+ date:
62
+ template:
63
+ default: "%{day} %{month} %{year} года"
64
+ nominative: "%{day} %{month} %{year} год"
65
+ months:
66
+ default: [ "января", "февраля", "марта", "апреля", "мая", "июня", "июля", "августа", "сентября", "октября", "ноября", "декабря" ]
67
+ nominative: [ "января", "февраля", "марта", "апреля", "мая", "июня", "июля", "августа", "сентября", "октября", "ноября", "декабря" ]
68
+ time:
69
+ template:
70
+ hours_only: "%{hours}"
71
+ hours_minutes: "%{hours} %{minutes}"
72
+ hours_minutes_seconds: "%{hours} %{minutes} %{seconds}"
73
+ words:
74
+ hour: ["час", "часа", "часов"]
75
+ minute: ["минута", "минуты", "минут"]
76
+ second: ["секунда", "секунды", "секунд"]
@@ -1,22 +1,29 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "date"
4
+ require "time"
4
5
 
5
6
  module Num2words
6
7
  class Converter
7
8
  class << self
8
9
  def to_words(number, *args, **opts)
9
- locale = args.first.is_a?(Symbol) ? args.first : opts[:locale] || I18n.default_locale
10
- feminine = opts[:feminine] || false
11
- style = opts[:style] || :fraction
12
- word_case = opts[:word_case] || :default
10
+ locale = args[0].is_a?(Symbol) ? args[0] : opts[:locale] || I18n.default_locale
11
+ type_only = args[1].is_a?(Symbol) ? args[1] : opts[:only]
12
+ type_short = args[2].is_a?(TrueClass) || args[2].is_a?(FalseClass) ? args[2] : opts[:short] || false
13
+
14
+ feminine = opts[:feminine] || false
15
+ style = opts[:style] || :fraction
16
+ word_case = opts[:word_case] || :default
13
17
  date_format = opts[:format] || :default
18
+
14
19
  locale_data = Locales[locale]
15
20
 
16
21
  result = case detect_type(number)
17
22
  when :float then to_words_fractional(number, locale, feminine, locale_data, style: style)
18
23
  when :integer then to_words_integer(number, locale, feminine, locale_data)
24
+ when :datetime then to_words_datetime(number, locale, locale_data, format: date_format, only: type_only, short: type_short)
19
25
  when :date then to_words_date(number, locale, locale_data, format: date_format)
26
+ when :time then to_words_time(number, locale, locale_data, short: type_short)
20
27
  else nil
21
28
  end
22
29
 
@@ -47,6 +54,7 @@ module Num2words
47
54
  private
48
55
 
49
56
  def pluralize(number, singular, few, plural)
57
+ number = number.abs
50
58
  return plural if (11..14).include?(number % 100)
51
59
 
52
60
  case number % 10
@@ -134,8 +142,10 @@ module Num2words
134
142
 
135
143
  day, month, year = [date.day, date.month, date.year]
136
144
 
145
+ return date.strftime("%d.%m.%Y") if format == :short
146
+
137
147
  months = locale_data::DATE[:months][format] || locale_data::DATE[:months][:default]
138
- template = locale_data::TEMPLATE[format] || locale_data::TEMPLATE[:default]
148
+ template = locale_data::DATE_TEMPLATE[format] || locale_data::DATE_TEMPLATE[:default]
139
149
 
140
150
  raise ArgumentError, "Months not found for locale #{locale}" unless months
141
151
  raise ArgumentError, "Template not found for locale #{locale}" unless template
@@ -170,6 +180,64 @@ module Num2words
170
180
  to_words_integer(value, locale, false, locale_data)
171
181
  end
172
182
 
183
+ def to_words_time(time, locale, locale_data, format: :default, short: false)
184
+ time = Time.parse(time) if time.is_a?(String)
185
+
186
+ return time.strftime("%H:%M") if format == :short
187
+
188
+ words = locale_data::TIME[:words]
189
+ template = locale_data::TIME_TEMPLATE
190
+
191
+ hours = [
192
+ to_words_integer(time.hour, locale, false, locale_data),
193
+ pluralize(time.hour, *words[:hour])
194
+ ].join(" ")
195
+ minutes = [
196
+ to_words_integer(time.min, locale, true, locale_data),
197
+ pluralize(time.min, *words[:minute])
198
+ ].join(" ")
199
+ seconds = [
200
+ to_words_integer(time.sec, locale, true, locale_data),
201
+ pluralize(time.sec, *words[:second])
202
+ ].join(" ")
203
+
204
+ format = if short
205
+ time.min.zero? && time.sec.zero? ? :hours_only : :hours_minutes
206
+ else
207
+ format
208
+ end
209
+
210
+ case format
211
+ when :hours_only
212
+ template[:hours_only] % { hours: hours }
213
+ when :hours_minutes
214
+ template[:hours_minutes] % { hours: hours, minutes: minutes }
215
+ when :hours_minutes_seconds, :default
216
+ template[:hours_minutes_seconds] % { hours: hours, minutes: minutes, seconds: seconds }
217
+ else
218
+ raise ArgumentError, "Unsupported time format: #{format}"
219
+ end
220
+ end
221
+
222
+
223
+ def to_words_datetime(datetime, locale, locale_data, format: :default, only: nil, short: false)
224
+ datetime = DateTime.parse(datetime) if datetime.is_a?(String)
225
+
226
+ date_format = short && only == :date ? :short : format
227
+ time_format = short && only == :time ? :short : :default
228
+
229
+ date_part = to_words_date(datetime.to_date, locale, locale_data, format: date_format)
230
+ time_part = to_words_time(datetime.to_time, locale, locale_data, format: time_format, short: short)
231
+
232
+ return date_part if only == :date
233
+ return time_part if only == :time
234
+
235
+ return "#{date_part}, #{time_part}" if short
236
+
237
+ template = locale_data::DATETIME_TEMPLATE
238
+ template % { date: date_part, time: time_part }
239
+ end
240
+
173
241
  def apply_case(text, word_case)
174
242
  case word_case
175
243
  when :upper then text.upcase
@@ -182,20 +250,30 @@ module Num2words
182
250
 
183
251
  def detect_type(value)
184
252
  case value
185
- when Integer then :integer
186
- when Float then :float
187
- when Date, Time, ::DateTime then :date
253
+ when Integer then :integer
254
+ when Float then :float
255
+ when Date then :date
256
+ when Time then :time
257
+ when DateTime then :datetime
188
258
  when String
189
- return :integer if /\A-?\d+\z/.match?(value)
190
- return :float if /\A-?\d+\.\d+\z/.match?(value)
259
+ return :integer if value.match?(/\A-?\d+\z/)
260
+ return :float if value.match?(/\A-?\d+\.\d+\z/)
261
+ return :time if value.match?(/\A\d{1,2}:\d{2}(:\d{2})?\z/)
262
+
263
+ # Форматы даты
264
+ return :date if value.match?(/\A\d{1,2}[.\-]\d{1,2}[.\-]\d{2,4}\z/)
265
+ return :date if value.match?(/\A\d{4}-\d{2}-\d{2}\z/)
266
+ return :datetime if value.match?(/\A\d{1,2}[.\-]\d{1,2}[.\-]\d{2,4}\s+\d{1,2}:\d{2}(:\d{2})?\z/)
267
+ return :datetime if value.match?(/\A\d{4}-\d{2}-\d{2}[ T]\d{2}:\d{2}(:\d{2})?([.,]\d+)?(Z|[+\-]\d{2}:?\d{2})?\z/)
191
268
 
192
269
  begin
193
- Date.parse(value)
194
- :date
270
+ date_time = DateTime.parse(value)
271
+ (date_time.hour != 0 || date_time.min != 0 || date_time.sec != 0) ? :datetime : :date
195
272
  rescue ArgumentError
196
273
  :string
197
274
  end
198
- else :unknown
275
+ else
276
+ :unknown
199
277
  end
200
278
  end
201
279
  end
@@ -31,3 +31,15 @@ class Date
31
31
  Num2words::Converter.to_words(self.strftime("%d.%m.%Y"), *args, **opts)
32
32
  end
33
33
  end
34
+
35
+ class Time
36
+ def to_words(*args, **opts)
37
+ Num2words::Converter.to_words(self.strftime("%H:%M:%S"), *args, **opts)
38
+ end
39
+ end
40
+
41
+ class DateTime
42
+ def to_words(*args, **opts)
43
+ Num2words::Converter.to_words(self.strftime("%d.%m.%Y %H:%M:%S"), *args, **opts)
44
+ end
45
+ end
@@ -17,7 +17,10 @@ module Num2words
17
17
  GRAMMAR = I18n.t("num2words.grammar", locale: :en)
18
18
 
19
19
  DATE = I18n.t("num2words.date", locale: :en)
20
- TEMPLATE = I18n.t("num2words.date.template", locale: :en)
20
+ DATE_TEMPLATE = I18n.t("num2words.date.template", locale: :en)
21
+ TIME = I18n.t("num2words.time", locale: :en)
22
+ TIME_TEMPLATE = I18n.t("num2words.time.template", locale: :en)
23
+ DATETIME_TEMPLATE = I18n.t("num2words.datetime.template", locale: :en)
21
24
 
22
25
  ORDINALS = I18n.t("num2words.numbers.ordinals", locale: :en)
23
26
  end
@@ -17,7 +17,10 @@ module Num2words
17
17
  GRAMMAR = I18n.t("num2words.grammar", locale: :ru)
18
18
 
19
19
  DATE = I18n.t("num2words.date", locale: :ru)
20
- TEMPLATE = I18n.t("num2words.date.template", locale: :ru)
20
+ DATE_TEMPLATE = I18n.t("num2words.date.template", locale: :ru)
21
+ TIME = I18n.t("num2words.time", locale: :ru)
22
+ TIME_TEMPLATE = I18n.t("num2words.time.template", locale: :ru)
23
+ DATETIME_TEMPLATE = I18n.t("num2words.datetime.template", locale: :ru)
21
24
 
22
25
  ORDINALS = I18n.t("num2words.numbers.ordinals", locale: :ru)
23
26
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Num2words
4
- VERSION = "0.1.4"
4
+ VERSION = "0.1.5"
5
5
  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.1.4
4
+ version: 0.1.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ruslan Fedotov
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2025-08-21 00:00:00.000000000 Z
11
+ date: 2025-08-22 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: i18n