num2words 0.2.0 → 0.3.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.
Files changed (108) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +22 -0
  3. data/README.md +113 -144
  4. data/config/locales/ar.yml +5 -1
  5. data/config/locales/be.yml +4 -0
  6. data/config/locales/bg.yml +4 -0
  7. data/config/locales/bn.yml +4 -0
  8. data/config/locales/cs.yml +6 -2
  9. data/config/locales/da.yml +4 -0
  10. data/config/locales/de.yml +8 -4
  11. data/config/locales/el.yml +4 -0
  12. data/config/locales/en.yml +4 -0
  13. data/config/locales/es.yml +4 -0
  14. data/config/locales/et.yml +9 -5
  15. data/config/locales/fa.yml +15 -11
  16. data/config/locales/fi.yml +8 -4
  17. data/config/locales/fr.yml +4 -0
  18. data/config/locales/gu.yml +4 -0
  19. data/config/locales/he.yml +6 -2
  20. data/config/locales/hi.yml +4 -0
  21. data/config/locales/hr.yml +8 -4
  22. data/config/locales/hu.yml +6 -2
  23. data/config/locales/id.yml +4 -0
  24. data/config/locales/it.yml +4 -0
  25. data/config/locales/ja.yml +10 -5
  26. data/config/locales/kn.yml +186 -10
  27. data/config/locales/ko.yml +184 -13
  28. data/config/locales/kz.yml +183 -17
  29. data/config/locales/lt.yml +184 -8
  30. data/config/locales/lv.yml +188 -12
  31. data/config/locales/ml.yml +189 -13
  32. data/config/locales/mr.yml +184 -8
  33. data/config/locales/ms.yml +184 -8
  34. data/config/locales/nb.yml +183 -17
  35. data/config/locales/nl.yml +184 -13
  36. data/config/locales/pa.yml +189 -13
  37. data/config/locales/pl.yml +184 -13
  38. data/config/locales/pt.yml +184 -13
  39. data/config/locales/ro.yml +183 -12
  40. data/config/locales/ru.yml +4 -0
  41. data/config/locales/sk.yml +184 -8
  42. data/config/locales/sl.yml +185 -9
  43. data/config/locales/sr.yml +186 -15
  44. data/config/locales/sv.yml +182 -13
  45. data/config/locales/sw.yml +184 -8
  46. data/config/locales/ta.yml +184 -8
  47. data/config/locales/te.yml +185 -9
  48. data/config/locales/th.yml +183 -12
  49. data/config/locales/tr.yml +183 -12
  50. data/config/locales/uk.yml +189 -18
  51. data/config/locales/ur.yml +187 -11
  52. data/config/locales/vi.yml +183 -12
  53. data/config/locales/zh.yml +191 -9
  54. data/lib/num2words/converter.rb +144 -35
  55. data/lib/num2words/locales/ar.rb +130 -0
  56. data/lib/num2words/locales/be.rb +101 -0
  57. data/lib/num2words/locales/bg.rb +127 -0
  58. data/lib/num2words/locales/bn.rb +112 -0
  59. data/lib/num2words/locales/cs.rb +106 -0
  60. data/lib/num2words/locales/da.rb +97 -0
  61. data/lib/num2words/locales/de.rb +119 -0
  62. data/lib/num2words/locales/el.rb +143 -0
  63. data/lib/num2words/locales/en.rb +90 -0
  64. data/lib/num2words/locales/es.rb +185 -0
  65. data/lib/num2words/locales/et.rb +104 -0
  66. data/lib/num2words/locales/fa.rb +104 -0
  67. data/lib/num2words/locales/fi.rb +104 -0
  68. data/lib/num2words/locales/fr.rb +121 -0
  69. data/lib/num2words/locales/gu.rb +112 -0
  70. data/lib/num2words/locales/he.rb +130 -0
  71. data/lib/num2words/locales/hi.rb +112 -0
  72. data/lib/num2words/locales/hr.rb +117 -0
  73. data/lib/num2words/locales/hu.rb +111 -0
  74. data/lib/num2words/locales/id.rb +104 -0
  75. data/lib/num2words/locales/it.rb +142 -0
  76. data/lib/num2words/locales/ja.rb +126 -0
  77. data/lib/num2words/locales/kn.rb +122 -2
  78. data/lib/num2words/locales/ko.rb +158 -2
  79. data/lib/num2words/locales/kz.rb +137 -2
  80. data/lib/num2words/locales/lt.rb +146 -2
  81. data/lib/num2words/locales/lv.rb +148 -2
  82. data/lib/num2words/locales/ml.rb +142 -2
  83. data/lib/num2words/locales/mr.rb +142 -2
  84. data/lib/num2words/locales/ms.rb +134 -2
  85. data/lib/num2words/locales/nb.rb +153 -0
  86. data/lib/num2words/locales/nl.rb +142 -2
  87. data/lib/num2words/locales/pa.rb +122 -2
  88. data/lib/num2words/locales/pl.rb +149 -2
  89. data/lib/num2words/locales/pt.rb +164 -2
  90. data/lib/num2words/locales/ro.rb +165 -2
  91. data/lib/num2words/locales/ru.rb +100 -0
  92. data/lib/num2words/locales/sk.rb +124 -2
  93. data/lib/num2words/locales/sl.rb +154 -2
  94. data/lib/num2words/locales/sr.rb +141 -3
  95. data/lib/num2words/locales/sv.rb +141 -2
  96. data/lib/num2words/locales/sw.rb +133 -2
  97. data/lib/num2words/locales/ta.rb +142 -2
  98. data/lib/num2words/locales/te.rb +142 -2
  99. data/lib/num2words/locales/th.rb +133 -2
  100. data/lib/num2words/locales/tr.rb +141 -2
  101. data/lib/num2words/locales/uk.rb +134 -2
  102. data/lib/num2words/locales/ur.rb +130 -2
  103. data/lib/num2words/locales/vi.rb +142 -2
  104. data/lib/num2words/locales/zh.rb +172 -2
  105. data/lib/num2words/version.rb +1 -1
  106. data/num2words.gemspec +2 -2
  107. metadata +6 -6
  108. data/lib/num2words/locales/no.rb +0 -19
@@ -10,8 +10,178 @@ module Num2words
10
10
  HUNDREDS = I18n.t("num2words.hundreds", locale: :zh)
11
11
  SCALES = I18n.t("num2words.scales", locale: :zh)
12
12
 
13
- MAJOR_UNIT = I18n.t("num2words.currencies.CNY.name", locale: :zh)
14
- MINOR_UNIT = I18n.t("num2words.currencies.CNY.fractional.name", locale: :zh)
13
+ FRACTIONS = I18n.t("num2words.fractions", locale: :zh)
14
+ GRAMMAR = I18n.t("num2words.grammar", locale: :zh)
15
+
16
+ DATE = I18n.t("num2words.date", locale: :zh)
17
+ DATE_TEMPLATE = I18n.t("num2words.date.template", locale: :zh)
18
+ TIME = I18n.t("num2words.time", locale: :zh)
19
+ TIME_TEMPLATE = I18n.t("num2words.time.template", locale: :zh)
20
+ DATETIME_TEMPLATE = I18n.t("num2words.datetime.template", locale: :zh)
21
+
22
+ ORDINALS = I18n.t("num2words.numbers.ordinals", locale: :zh)
23
+
24
+ LARGE_SCALES = ["", "万", "亿", "兆", "京"].freeze
25
+ SMALL_UNITS = ["", "十", "百", "千"].freeze
26
+
27
+ module_function
28
+
29
+ def integer_to_words(number, feminine: false)
30
+ cardinal(number)
31
+ end
32
+
33
+ def triple_to_words(number, scale_idx, feminine: false)
34
+ return [] if number.zero?
35
+
36
+ ["#{under_ten_thousand(number)}#{LARGE_SCALES[scale_idx]}"]
37
+ end
38
+
39
+ def minus_word
40
+ GRAMMAR[:minus]
41
+ end
42
+
43
+ def fraction_joiner(joiner)
44
+ joiner.to_sym == :and ? "又" : GRAMMAR[:conjunction]
45
+ end
46
+
47
+ def default_fraction_word
48
+ GRAMMAR[:default_fraction]
49
+ end
50
+
51
+ def fraction_numerator_feminine?
52
+ false
53
+ end
54
+
55
+ def decimal_separator_word
56
+ GRAMMAR[:decimal_separator] || "点"
57
+ end
58
+
59
+ def decimal_fraction_words(fraction_string)
60
+ fraction_string.chars.map { |digit| ONES_MASC[digit.to_i] }.join
61
+ end
62
+
63
+ def join_decimal_words(words)
64
+ words.compact.reject(&:empty?).join
65
+ end
66
+
67
+ def join_fraction_words(words)
68
+ sign_word, integer_words, conjunction_word, numerator_words, denominator_words = words
69
+ numerator_words = "" if denominator_words.to_s.end_with?(numerator_words.to_s)
70
+
71
+ [sign_word, integer_words, conjunction_word, denominator_words, numerator_words].compact.reject(&:empty?).join
72
+ end
73
+
74
+ def date_day(day, format:, date_case:)
75
+ cardinal(day)
76
+ end
77
+
78
+ def date_year(year, format:)
79
+ cardinal(year)
80
+ end
81
+
82
+ def time_unit_feminine?(_unit)
83
+ false
84
+ end
85
+
86
+ def time_number_words(number, unit:)
87
+ cardinal(number)
88
+ end
89
+
90
+ def join_time_words(number_words, unit_words)
91
+ "#{number_words}#{unit_words}"
92
+ end
93
+
94
+ def currency_major_feminine?(_currency)
95
+ false
96
+ end
97
+
98
+ def currency_minor_feminine?(_currency)
99
+ false
100
+ end
101
+
102
+ def currency_number_words(number, _currency, unit:)
103
+ cardinal(number)
104
+ end
105
+
106
+ def join_currency_parts(parts)
107
+ parts.join
108
+ end
109
+
110
+ def ordinal(value, format, gender: :masculine)
111
+ ordinals = ORDINALS[format] || ORDINALS[:default] || ORDINALS[:nominative]
112
+ gender_data = ordinals[gender] || ordinals[:masculine]
113
+
114
+ return gender_data[value - 1] if value.between?(1, gender_data.length)
115
+
116
+ "第#{cardinal(value)}"
117
+ end
118
+
119
+ def cardinal(number)
120
+ integer_value = Integer(number)
121
+ negative = integer_value.negative?
122
+ integer_value = integer_value.abs
123
+
124
+ return ONES_MASC[0] if integer_value.zero?
125
+
126
+ groups = integer_value.to_s
127
+ .chars.reverse.each_slice(4).map(&:reverse)
128
+ .map(&:join).map!(&:to_i).reverse
129
+
130
+ words = []
131
+ zero_between = false
132
+
133
+ groups.each_with_index do |group_value, index|
134
+ scale_idx = groups.size - index - 1
135
+
136
+ if group_value.zero?
137
+ zero_between = words.any?
138
+ next
139
+ end
140
+
141
+ words << ONES_MASC[0] if words.any? && (zero_between || group_value < 1000) && words.last != ONES_MASC[0]
142
+ words << "#{under_ten_thousand(group_value)}#{LARGE_SCALES.fetch(scale_idx, '')}"
143
+ zero_between = false
144
+ end
145
+
146
+ words.unshift(minus_word) if negative
147
+ words.join
148
+ end
149
+
150
+ def under_ten_thousand(number)
151
+ digits = number.digits
152
+ words = []
153
+ zero_needed = false
154
+
155
+ (3).downto(0) do |position|
156
+ digit = digits[position].to_i
157
+
158
+ if digit.zero?
159
+ zero_needed = words.any? && lower_nonzero?(digits, position)
160
+ next
161
+ end
162
+
163
+ words << ONES_MASC[0] if zero_needed
164
+ words << unit_words(digit, position, words.empty?)
165
+ zero_needed = false
166
+ end
167
+
168
+ words.join
169
+ end
170
+
171
+ def unit_words(digit, position, group_start)
172
+ return ONES_MASC[digit] if position.zero?
173
+ return SMALL_UNITS[position] if position == 1 && digit == 1 && group_start
174
+
175
+ "#{ONES_MASC[digit]}#{SMALL_UNITS[position]}"
176
+ end
177
+
178
+ def lower_nonzero?(digits, position)
179
+ (0...position).any? { |lower_position| digits[lower_position].to_i.positive? }
180
+ end
181
+
182
+ def pluralize(number, singular, few, _plural)
183
+ number == 1 ? singular : few
184
+ end
15
185
  end
16
186
 
17
187
  register :zh, ZH
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Num2words
4
- VERSION = "0.2.0"
4
+ VERSION = "0.3.0"
5
5
  end
data/num2words.gemspec CHANGED
@@ -6,8 +6,8 @@ Gem::Specification.new do |spec|
6
6
  spec.authors = ["Ruslan Fedotov"]
7
7
  spec.email = ["progruson@gmail.com"]
8
8
 
9
- spec.summary = %q{Russian number-to-words with currency (руб/коп) and correct declensions}
10
- spec.description = %q{Converts integers and amounts to Russian words with proper gender and plural forms.}
9
+ spec.summary = %q{Convert numbers, amounts, dates, and times to words}
10
+ spec.description = %q{Converts numbers, currency amounts, dates, and times to words with locale-aware grammar across supported locales.}
11
11
  spec.homepage = "https://github.com/skyrusx/num2words"
12
12
  spec.license = "MIT"
13
13
  spec.required_ruby_version = Gem::Requirement.new(">= 2.3.0")
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.2.0
4
+ version: 0.3.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-05-31 00:00:00.000000000 Z
11
+ date: 2026-06-27 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: i18n
@@ -24,8 +24,8 @@ dependencies:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
26
  version: '1.8'
27
- description: Converts integers and amounts to Russian words with proper gender and
28
- plural forms.
27
+ description: Converts numbers, currency amounts, dates, and times to words with locale-aware
28
+ grammar across supported locales.
29
29
  email:
30
30
  - progruson@gmail.com
31
31
  executables:
@@ -129,8 +129,8 @@ files:
129
129
  - lib/num2words/locales/ml.rb
130
130
  - lib/num2words/locales/mr.rb
131
131
  - lib/num2words/locales/ms.rb
132
+ - lib/num2words/locales/nb.rb
132
133
  - lib/num2words/locales/nl.rb
133
- - lib/num2words/locales/no.rb
134
134
  - lib/num2words/locales/pa.rb
135
135
  - lib/num2words/locales/pl.rb
136
136
  - lib/num2words/locales/pt.rb
@@ -176,5 +176,5 @@ requirements: []
176
176
  rubygems_version: 3.1.2
177
177
  signing_key:
178
178
  specification_version: 4
179
- summary: Russian number-to-words with currency (руб/коп) and correct declensions
179
+ summary: Convert numbers, amounts, dates, and times to words
180
180
  test_files: []
@@ -1,19 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Num2words
4
- module Locales
5
- module NB
6
- ONES_MASC = I18n.t("num2words.ones_masc", locale: :nb)
7
- ONES_FEM = I18n.t("num2words.ones_fem", locale: :nb)
8
- TEENS = I18n.t("num2words.teens", locale: :nb)
9
- TENS = I18n.t("num2words.tens", locale: :nb)
10
- HUNDREDS = I18n.t("num2words.hundreds", locale: :nb)
11
- SCALES = I18n.t("num2words.scales", locale: :nb)
12
-
13
- MAJOR_UNIT = I18n.t("num2words.currencies.NOK.name", locale: :nb)
14
- MINOR_UNIT = I18n.t("num2words.currencies.NOK.fractional.name", locale: :nb)
15
- end
16
-
17
- register :nb, NB
18
- end
19
- end