num2words 0.1.3 → 0.1.4

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: 83b33cb28c58663786a3033b2aefceb7e0478d49d40b1db5b1bee5bc63f0198b
4
- data.tar.gz: 5293c916f1a5ad2726662cb5973033b23fa5f9b703920b2b4be21f293d47d67e
3
+ metadata.gz: 1809289bec98157e0b78b8bbe9f18b5fab8fa5f111293105e89b5747d866e43d
4
+ data.tar.gz: 1e5cfdd50831c7e9f75a8216488268564cda27b7972c70feca0a9d34c05c907b
5
5
  SHA512:
6
- metadata.gz: c498c97a62099658e43c6d29159b9c6fc5c8d3113e19fdc0fe774966dbacce7084d8e0e5293e1a9b1b9cad5b0441af1ae35efe1922441a19196fd49f0d771756
7
- data.tar.gz: 53ceaa5a779dd1e7c3e8308d4f21dcafe2a3cde1a775ddb68fb7497be605d551497ee22350c3309feb4f21c648ff9546761c427ff5839ae43e0c01f073febc08
6
+ metadata.gz: 353353c876466e244bf2748073d7a3534a5ead257bc8d7f70f35d975a45762f4d2e0023d2cbf6e55136587974278093987fd2bba4d20ec972f33bedc9a1820c2
7
+ data.tar.gz: 8037de95f9896db0f4b00aa16710836de2337416c646dfc40900f3021a019aba9c04ae7f06842ebc6be73904aca9156258ee766992a861196a3274008ca13ab4
data/CHANGELOG.md CHANGED
@@ -15,6 +15,33 @@
15
15
 
16
16
  ---
17
17
 
18
+ ## [0.1.4] - 2025-08-21
19
+ ### Added
20
+ - Расширение `String#to_words`, возможность преобразовать строки с датами
21
+ ```ruby
22
+ "21.08.2025".to_words(:ru)
23
+ # => "двадцать первое августа две тысячи двадцать пятого года"
24
+
25
+ "21.08.2025".to_words(:en)
26
+ # => "the twenty-first of August, two thousand twenty five"
27
+ ```
28
+ - Расширение `Date#to_words`, добавлена возможность преобразовывать объекты Date и DateTime
29
+ ```ruby
30
+ Date.new(2025, 8, 21).to_words(:ru)
31
+ # => "двадцать первого августа две тысячи двадцать пятого года"
32
+
33
+ Date.new(2025, 8, 21).to_words(:en)
34
+ # => "the twenty-first of August, two thousand twenty-five"
35
+ ```
36
+ - Поддержка параметра `format: :nominative`, можно получать даты в именительном падеже.
37
+ ```ruby
38
+ Date.new(2025, 8, 21).to_words(:ru, format: :nominative)
39
+ # => "двадцать первое августа две тысячи двадцать пятый год"
40
+
41
+ "19.07.2012".to_words(:ru, format: :nominative)
42
+ # => "девятнадцатое июля две тысячи двенадцатый год"
43
+ ```
44
+
18
45
  ## [0.1.3] – 2025-08-20
19
46
  ### Added
20
47
  - Параметр **`word_case:`** для методов `to_words`, `to_currency` со значениями:
@@ -39,3 +39,17 @@ en:
39
39
  10000000: ["ten-millionth", "ten-millionths", "ten-millionths"]
40
40
  100000000: ["hundred-millionth", "hundred-millionths", "hundred-millionths"]
41
41
  1000000000: ["billionth", "billionths", "billionths"]
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
+ numbers:
50
+ ordinals:
51
+ default:
52
+ masculine: ["first", "second", "third", "fourth", "fifth", "sixth", "seventh", "eighth", "ninth",
53
+ "tenth", "eleventh", "twelfth", "thirteenth", "fourteenth" ,"fifteenth", "sixteenth", "seventeenth", "eighteenth", "nineteenth",
54
+ "twentieth", "twenty-first", "twenty-second", "twenty-third", "twenty-fourth", "twenty-fifth", "twenty-sixth", "twenty-seventh", "twenty-eighth", "twenty-ninth",
55
+ "thirtieth","thirty-first"]
@@ -34,3 +34,32 @@ ru:
34
34
  10000000: ["десятимиллионная", "десятимиллионные", "десятимиллионных"]
35
35
  100000000: ["стомиллионная", "стомиллионные", "стомиллионных"]
36
36
  1000000000: ["миллиардная", "миллиардные", "миллиардных"]
37
+
38
+ date:
39
+ template:
40
+ default: "%{day} %{month} %{year} года"
41
+ nominative: "%{day} %{month} %{year} год"
42
+ months:
43
+ default: ["января", "февраля", "марта", "апреля", "мая", "июня", "июля", "августа", "сентября", "октября", "ноября", "декабря"]
44
+ nominative: ["января", "февраля", "марта", "апреля", "мая", "июня", "июля", "августа", "сентября", "октября", "ноября", "декабря"]
45
+
46
+ numbers:
47
+ ordinals:
48
+ nominative:
49
+ masculine: ["первый", "второй", "третий", "четвёртый", "пятый", "шестой", "седьмой", "восьмой", "девятый", "десятый",
50
+ "одиннадцатый", "двенадцатый", "тринадцатый", "четырнадцатый", "пятнадцатый", "шестнадцатый", "семнадцатый", "восемнадцатый", "девятнадцатый",
51
+ "двадцатый", "двадцать первый", "двадцать второй", "двадцать третий", "двадцать четвёртый", "двадцать пятый", "двадцать шестой", "двадцать седьмой", "двадцать восьмой", "двадцать девятый",
52
+ "тридцатый", "тридцать первый"]
53
+ feminine: ["первая", "вторая", "третья", "четвёртая", "пятая", "шестая", "седьмая", "восьмая", "девятая",
54
+ "десятая", "одиннадцатая", "двенадцатая", "тринадцатая", "четырнадцатая", "пятнадцатая", "шестнадцатая", "семнадцатая", "восемнадцатая", "девятнадцатая",
55
+ "двадцатая", "двадцать первая", "двадцать вторая", "двадцать третья", "двадцать четвёртая", "двадцать пятая", "двадцать шестая", "двадцать седьмая", "двадцать восьмая", "двадцать девятая",
56
+ "тридцатая", "тридцать первая"]
57
+ neuter: ["первое", "второе", "третье", "четвёртое", "пятое", "шестое", "седьмое", "восьмое", "девятое",
58
+ "десятое", "одиннадцатое", "двенадцатое", "тринадцатое", "четырнадцатое", "пятнадцатое", "шестнадцатое", "семнадцатое", "восемнадцатое", "девятнадцатое",
59
+ "двадцатое", "двадцать первое", "двадцать второе", "двадцать третье", "двадцать четвёртое", "двадцать пятое", "двадцать шестое", "двадцать седьмое", "двадцать восьмое", "двадцать девятое",
60
+ "тридцатое", "тридцать первое"]
61
+ default:
62
+ masculine: ["первого", "второго", "третьего", "четвёртого", "пятого", "шестого", "седьмого", "восьмого", "девятого",
63
+ "десятого", "одиннадцатого", "двенадцатого", "тринадцатого", "четырнадцатого", "пятнадцатого", "шестнадцатого", "семнадцатого", "восемнадцатого", "девятнадцатого",
64
+ "двадцатого", "двадцать первого", "двадцать второго", "двадцать третьего", "двадцать четвёртого", "двадцать пятого", "двадцать шестого", "двадцать седьмого", "двадцать восьмого", "двадцать девятого",
65
+ "тридцатого", "тридцать первого"]
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "date"
4
+
3
5
  module Num2words
4
6
  class Converter
5
7
  class << self
@@ -7,15 +9,19 @@ module Num2words
7
9
  locale = args.first.is_a?(Symbol) ? args.first : opts[:locale] || I18n.default_locale
8
10
  feminine = opts[:feminine] || false
9
11
  style = opts[:style] || :fraction
10
- word_case = opts[:word_case] || :downcase
12
+ word_case = opts[:word_case] || :default
13
+ date_format = opts[:format] || :default
11
14
  locale_data = Locales[locale]
12
15
 
13
- result = if number.is_a?(Float)
14
- to_words_fractional(number, locale, feminine, locale_data, style: style)
15
- else
16
- to_words_integer(number, locale, feminine, locale_data)
16
+ result = case detect_type(number)
17
+ when :float then to_words_fractional(number, locale, feminine, locale_data, style: style)
18
+ when :integer then to_words_integer(number, locale, feminine, locale_data)
19
+ when :date then to_words_date(number, locale, locale_data, format: date_format)
20
+ else nil
17
21
  end
18
22
 
23
+ raise ArgumentError, "Unsupported input type: #{number.inspect}" if result.nil?
24
+
19
25
  apply_case(result, word_case)
20
26
  end
21
27
 
@@ -123,6 +129,47 @@ module Num2words
123
129
  words.join(" ")
124
130
  end
125
131
 
132
+ def to_words_date(date, locale, locale_data, format: :default)
133
+ date = Date.parse(date.to_s) unless date.is_a?(Date)
134
+
135
+ day, month, year = [date.day, date.month, date.year]
136
+
137
+ months = locale_data::DATE[:months][format] || locale_data::DATE[:months][:default]
138
+ template = locale_data::TEMPLATE[format] || locale_data::TEMPLATE[:default]
139
+
140
+ raise ArgumentError, "Months not found for locale #{locale}" unless months
141
+ raise ArgumentError, "Template not found for locale #{locale}" unless template
142
+
143
+ day_words = to_words_ordinal(day, locale, format, locale_data, gender: :neuter)
144
+ month_words = months[month - 1]
145
+ year_words = to_words_ordinal(year, locale, format, locale_data)
146
+
147
+ template % { day: day_words, month: month_words, year: year_words }
148
+ end
149
+
150
+ def to_words_ordinal(value, locale, format, locale_data, gender: :masculine)
151
+ ordinals = locale_data::ORDINALS[format] rescue nil
152
+ raise ArgumentError, "Ordinals not found for locale #{locale}, format #{format}" unless ordinals
153
+
154
+ gender_data = ordinals[gender] || ordinals[:masculine]
155
+ raise ArgumentError, "Gender #{gender} not found for locale #{locale}, format #{format}" unless gender_data
156
+
157
+ return gender_data[value - 1] if gender_data[value]
158
+
159
+ if value > 31
160
+ thousands = (value / 100) * 100
161
+ last_two = value % 100
162
+
163
+ base_year = to_words_integer(thousands, locale, false, locale_data)
164
+ last_ordinal = gender_data[last_two - 1] || to_words_integer(last_two, locale, false, locale_data)
165
+ last_ordinal = to_words_integer(last_two, locale, false, locale_data) if locale == :en
166
+
167
+ return [base_year, last_ordinal].join(" ")
168
+ end
169
+
170
+ to_words_integer(value, locale, false, locale_data)
171
+ end
172
+
126
173
  def apply_case(text, word_case)
127
174
  case word_case
128
175
  when :upper then text.upcase
@@ -132,6 +179,25 @@ module Num2words
132
179
  else text
133
180
  end
134
181
  end
182
+
183
+ def detect_type(value)
184
+ case value
185
+ when Integer then :integer
186
+ when Float then :float
187
+ when Date, Time, ::DateTime then :date
188
+ when String
189
+ return :integer if /\A-?\d+\z/.match?(value)
190
+ return :float if /\A-?\d+\.\d+\z/.match?(value)
191
+
192
+ begin
193
+ Date.parse(value)
194
+ :date
195
+ rescue ArgumentError
196
+ :string
197
+ end
198
+ else :unknown
199
+ end
200
+ end
135
201
  end
136
202
  end
137
203
  end
@@ -19,3 +19,15 @@ class Float
19
19
  Num2words::Converter.to_currency(self, *args, **opts)
20
20
  end
21
21
  end
22
+
23
+ class String
24
+ def to_words(*args, **opts)
25
+ Num2words::Converter.to_words(self, *args, **opts)
26
+ end
27
+ end
28
+
29
+ class Date
30
+ def to_words(*args, **opts)
31
+ Num2words::Converter.to_words(self.strftime("%d.%m.%Y"), *args, **opts)
32
+ end
33
+ end
@@ -15,6 +15,11 @@ module Num2words
15
15
 
16
16
  FRACTIONS = I18n.t("num2words.fractions", locale: :en)
17
17
  GRAMMAR = I18n.t("num2words.grammar", locale: :en)
18
+
19
+ DATE = I18n.t("num2words.date", locale: :en)
20
+ TEMPLATE = I18n.t("num2words.date.template", locale: :en)
21
+
22
+ ORDINALS = I18n.t("num2words.numbers.ordinals", locale: :en)
18
23
  end
19
24
 
20
25
  register :en, EN
@@ -15,6 +15,11 @@ module Num2words
15
15
 
16
16
  FRACTIONS = I18n.t("num2words.fractions", locale: :ru)
17
17
  GRAMMAR = I18n.t("num2words.grammar", locale: :ru)
18
+
19
+ DATE = I18n.t("num2words.date", locale: :ru)
20
+ TEMPLATE = I18n.t("num2words.date.template", locale: :ru)
21
+
22
+ ORDINALS = I18n.t("num2words.numbers.ordinals", locale: :ru)
18
23
  end
19
24
 
20
25
  register :ru, RU
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Num2words
4
- VERSION = "0.1.3"
4
+ VERSION = "0.1.4"
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.3
4
+ version: 0.1.4
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-20 00:00:00.000000000 Z
11
+ date: 2025-08-21 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: i18n