num2words 0.1.6 → 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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +65 -0
- data/README.md +145 -135
- data/bin/console +2 -14
- data/config/locales/ar.yml +5 -1
- data/config/locales/be.yml +4 -0
- data/config/locales/bg.yml +4 -0
- data/config/locales/bn.yml +4 -0
- data/config/locales/cs.yml +6 -2
- data/config/locales/da.yml +4 -0
- data/config/locales/de.yml +8 -4
- data/config/locales/el.yml +4 -0
- data/config/locales/en.yml +4 -0
- data/config/locales/es.yml +4 -0
- data/config/locales/et.yml +9 -5
- data/config/locales/fa.yml +15 -11
- data/config/locales/fi.yml +8 -4
- data/config/locales/fr.yml +4 -0
- data/config/locales/gu.yml +4 -0
- data/config/locales/he.yml +6 -2
- data/config/locales/hi.yml +4 -0
- data/config/locales/hr.yml +8 -4
- data/config/locales/hu.yml +6 -2
- data/config/locales/id.yml +4 -0
- data/config/locales/it.yml +4 -0
- data/config/locales/ja.yml +10 -5
- data/config/locales/kn.yml +186 -10
- data/config/locales/ko.yml +184 -13
- data/config/locales/kz.yml +183 -17
- data/config/locales/lt.yml +184 -8
- data/config/locales/lv.yml +188 -12
- data/config/locales/ml.yml +189 -13
- data/config/locales/mr.yml +184 -8
- data/config/locales/ms.yml +184 -8
- data/config/locales/nb.yml +183 -17
- data/config/locales/nl.yml +184 -13
- data/config/locales/pa.yml +189 -13
- data/config/locales/pl.yml +184 -13
- data/config/locales/pt.yml +184 -13
- data/config/locales/ro.yml +183 -12
- data/config/locales/ru.yml +8 -0
- data/config/locales/sk.yml +184 -8
- data/config/locales/sl.yml +185 -9
- data/config/locales/sr.yml +186 -15
- data/config/locales/sv.yml +182 -13
- data/config/locales/sw.yml +184 -8
- data/config/locales/ta.yml +184 -8
- data/config/locales/te.yml +185 -9
- data/config/locales/th.yml +183 -12
- data/config/locales/tr.yml +183 -12
- data/config/locales/uk.yml +189 -18
- data/config/locales/ur.yml +187 -11
- data/config/locales/vi.yml +183 -12
- data/config/locales/zh.yml +191 -9
- data/exe/num2words-console +7 -0
- data/lib/num2words/console.rb +16 -0
- data/lib/num2words/converter.rb +195 -47
- data/lib/num2words/locales/ar.rb +130 -0
- data/lib/num2words/locales/be.rb +101 -0
- data/lib/num2words/locales/bg.rb +127 -0
- data/lib/num2words/locales/bn.rb +112 -0
- data/lib/num2words/locales/cs.rb +106 -0
- data/lib/num2words/locales/da.rb +97 -0
- data/lib/num2words/locales/de.rb +119 -0
- data/lib/num2words/locales/el.rb +143 -0
- data/lib/num2words/locales/en.rb +90 -0
- data/lib/num2words/locales/es.rb +185 -0
- data/lib/num2words/locales/et.rb +104 -0
- data/lib/num2words/locales/fa.rb +104 -0
- data/lib/num2words/locales/fi.rb +104 -0
- data/lib/num2words/locales/fr.rb +121 -0
- data/lib/num2words/locales/gu.rb +112 -0
- data/lib/num2words/locales/he.rb +130 -0
- data/lib/num2words/locales/hi.rb +112 -0
- data/lib/num2words/locales/hr.rb +117 -0
- data/lib/num2words/locales/hu.rb +111 -0
- data/lib/num2words/locales/id.rb +104 -0
- data/lib/num2words/locales/it.rb +142 -0
- data/lib/num2words/locales/ja.rb +126 -0
- data/lib/num2words/locales/kn.rb +122 -2
- data/lib/num2words/locales/ko.rb +158 -2
- data/lib/num2words/locales/kz.rb +137 -2
- data/lib/num2words/locales/lt.rb +146 -2
- data/lib/num2words/locales/lv.rb +148 -2
- data/lib/num2words/locales/ml.rb +142 -2
- data/lib/num2words/locales/mr.rb +142 -2
- data/lib/num2words/locales/ms.rb +134 -2
- data/lib/num2words/locales/nb.rb +153 -0
- data/lib/num2words/locales/nl.rb +142 -2
- data/lib/num2words/locales/pa.rb +122 -2
- data/lib/num2words/locales/pl.rb +149 -2
- data/lib/num2words/locales/pt.rb +164 -2
- data/lib/num2words/locales/ro.rb +165 -2
- data/lib/num2words/locales/ru.rb +100 -0
- data/lib/num2words/locales/sk.rb +124 -2
- data/lib/num2words/locales/sl.rb +154 -2
- data/lib/num2words/locales/sr.rb +141 -3
- data/lib/num2words/locales/sv.rb +141 -2
- data/lib/num2words/locales/sw.rb +133 -2
- data/lib/num2words/locales/ta.rb +142 -2
- data/lib/num2words/locales/te.rb +142 -2
- data/lib/num2words/locales/th.rb +133 -2
- data/lib/num2words/locales/tr.rb +141 -2
- data/lib/num2words/locales/uk.rb +134 -2
- data/lib/num2words/locales/ur.rb +130 -2
- data/lib/num2words/locales/vi.rb +142 -2
- data/lib/num2words/locales/zh.rb +172 -2
- data/lib/num2words/version.rb +1 -1
- data/num2words.gemspec +3 -3
- metadata +13 -10
- data/lib/num2words/locales/no.rb +0 -19
data/lib/num2words/converter.rb
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
require "date"
|
|
4
4
|
require "time"
|
|
5
|
+
require "bigdecimal"
|
|
5
6
|
|
|
6
7
|
module Num2words
|
|
7
8
|
class Converter
|
|
@@ -15,14 +16,19 @@ module Num2words
|
|
|
15
16
|
style = opts[:style] || :fraction
|
|
16
17
|
word_case = opts[:word_case] || :default
|
|
17
18
|
date_format = opts[:format] || :default
|
|
19
|
+
date_case = opts[:date_case] || :default
|
|
20
|
+
joiner = opts[:joiner] || :default
|
|
21
|
+
|
|
22
|
+
validate_option!(:date_case, date_case, %i[default genitive])
|
|
23
|
+
validate_option!(:joiner, joiner, %i[default and])
|
|
18
24
|
|
|
19
25
|
locale_data = Locales[locale]
|
|
20
26
|
|
|
21
27
|
result = case detect_type(number)
|
|
22
|
-
when :float then to_words_fractional(number, locale, feminine, locale_data, style: style)
|
|
28
|
+
when :float then to_words_fractional(number, locale, feminine, locale_data, style: style, joiner: joiner)
|
|
23
29
|
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)
|
|
25
|
-
when :date then to_words_date(number, locale, locale_data, format: date_format)
|
|
30
|
+
when :datetime then to_words_datetime(number, locale, locale_data, format: date_format, only: type_only, short: type_short, date_case: date_case)
|
|
31
|
+
when :date then to_words_date(number, locale, locale_data, format: date_format, date_case: date_case)
|
|
26
32
|
when :time then to_words_time(number, locale, locale_data, short: type_short)
|
|
27
33
|
else nil
|
|
28
34
|
end
|
|
@@ -36,6 +42,9 @@ module Num2words
|
|
|
36
42
|
locale = args.first.is_a?(Symbol) ? args.first : opts[:locale] || I18n.default_locale
|
|
37
43
|
word_case = opts[:word_case] || :downcase
|
|
38
44
|
currency = (opts[:code] || Num2words.default_currency(locale)).to_s.upcase.to_sym
|
|
45
|
+
minor = opts[:minor] || :always
|
|
46
|
+
|
|
47
|
+
validate_option!(:minor, minor, %i[always nonzero never])
|
|
39
48
|
|
|
40
49
|
unless Num2words.available_currencies(locale).include?(currency)
|
|
41
50
|
warn I18n.t("num2words.warnings.currency_not_available",
|
|
@@ -46,22 +55,46 @@ module Num2words
|
|
|
46
55
|
currency_data = I18n.t("num2words.currencies.#{currency}", locale: locale) or
|
|
47
56
|
raise ArgumentError, "Currency #{currency} not defined in locale #{locale}"
|
|
48
57
|
|
|
49
|
-
|
|
58
|
+
decimal_amount = decimal_currency_amount(amount)
|
|
59
|
+
major_value, minor_value = format('%.2f', decimal_amount.abs).split('.').map(&:to_i)
|
|
60
|
+
major_feminine = locale_currency_major_feminine?(Locales[locale], currency)
|
|
61
|
+
minor_feminine = locale_currency_minor_feminine?(Locales[locale], currency)
|
|
50
62
|
|
|
51
63
|
parts = [
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
to_words(minor_value, locale: locale, feminine: true),
|
|
55
|
-
pluralize(minor_value, *currency_data[:minor_unit])
|
|
64
|
+
locale_currency_number_words(Locales[locale], major_value, currency, unit: :major, locale: locale, feminine: major_feminine),
|
|
65
|
+
locale_pluralize(Locales[locale], major_value, currency_data[:major_unit])
|
|
56
66
|
]
|
|
57
67
|
|
|
58
|
-
|
|
68
|
+
include_minor = minor == :always || (minor == :nonzero && minor_value.positive?)
|
|
69
|
+
if include_minor
|
|
70
|
+
parts.concat([
|
|
71
|
+
locale_currency_number_words(Locales[locale], minor_value, currency, unit: :minor, locale: locale, feminine: minor_feminine),
|
|
72
|
+
locale_pluralize(Locales[locale], minor_value, currency_data[:minor_unit])
|
|
73
|
+
])
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
parts.unshift(locale_minus_word(Locales[locale])) if decimal_amount.negative?
|
|
59
77
|
|
|
60
|
-
apply_case(
|
|
78
|
+
apply_case(locale_join_currency_parts(Locales[locale], parts), word_case)
|
|
61
79
|
end
|
|
62
80
|
|
|
63
81
|
private
|
|
64
82
|
|
|
83
|
+
def validate_option!(name, value, allowed_values)
|
|
84
|
+
return if allowed_values.include?(value)
|
|
85
|
+
|
|
86
|
+
raise ArgumentError, "Unsupported #{name} option: #{value.inspect}"
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def decimal_currency_amount(amount)
|
|
90
|
+
return amount if amount.is_a?(BigDecimal)
|
|
91
|
+
|
|
92
|
+
normalized_amount = amount.is_a?(String) ? amount.tr(",", ".") : amount.to_s
|
|
93
|
+
BigDecimal(normalized_amount)
|
|
94
|
+
rescue ArgumentError
|
|
95
|
+
raise ArgumentError, "Unsupported currency amount: #{amount.inspect}"
|
|
96
|
+
end
|
|
97
|
+
|
|
65
98
|
def pluralize(number, singular, few, plural)
|
|
66
99
|
number = number.abs
|
|
67
100
|
return plural if (11..14).include?(number % 100)
|
|
@@ -73,10 +106,103 @@ module Num2words
|
|
|
73
106
|
end
|
|
74
107
|
end
|
|
75
108
|
|
|
109
|
+
def locale_pluralize(locale_data, number, forms)
|
|
110
|
+
return locale_data.pluralize(number, *forms) if locale_data.respond_to?(:pluralize)
|
|
111
|
+
|
|
112
|
+
pluralize(number, *forms)
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def locale_currency_major_feminine?(locale_data, currency)
|
|
116
|
+
return locale_data.currency_major_feminine?(currency) if locale_data.respond_to?(:currency_major_feminine?)
|
|
117
|
+
|
|
118
|
+
false
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def locale_currency_minor_feminine?(locale_data, currency)
|
|
122
|
+
return locale_data.currency_minor_feminine?(currency) if locale_data.respond_to?(:currency_minor_feminine?)
|
|
123
|
+
|
|
124
|
+
true
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
def locale_currency_number_words(locale_data, number, currency, unit:, locale:, feminine:)
|
|
128
|
+
if locale_data.respond_to?(:currency_number_words)
|
|
129
|
+
return locale_data.currency_number_words(number, currency, unit: unit)
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
to_words(number, locale: locale, feminine: feminine)
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
def locale_join_currency_parts(locale_data, parts)
|
|
136
|
+
return locale_data.join_currency_parts(parts) if locale_data.respond_to?(:join_currency_parts)
|
|
137
|
+
|
|
138
|
+
parts.join(" ").strip
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
def locale_time_unit_feminine?(locale_data, unit, default)
|
|
142
|
+
return locale_data.time_unit_feminine?(unit) if locale_data.respond_to?(:time_unit_feminine?)
|
|
143
|
+
|
|
144
|
+
default
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
def locale_time_number_words(locale_data, number, unit, locale, feminine)
|
|
148
|
+
if locale_data.respond_to?(:time_number_words)
|
|
149
|
+
return locale_data.time_number_words(number, unit: unit)
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
to_words_integer(number, locale, feminine, locale_data)
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
def locale_join_time_words(locale_data, number_words, unit_words)
|
|
156
|
+
return locale_data.join_time_words(number_words, unit_words) if locale_data.respond_to?(:join_time_words)
|
|
157
|
+
|
|
158
|
+
[number_words, unit_words].join(" ")
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
def locale_minus_word(locale_data)
|
|
162
|
+
return locale_data.minus_word if locale_data.respond_to?(:minus_word)
|
|
163
|
+
|
|
164
|
+
locale_data::GRAMMAR[:minus]
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
def locale_fraction_joiner(locale_data, joiner)
|
|
168
|
+
return locale_data.fraction_joiner(joiner) if locale_data.respond_to?(:fraction_joiner)
|
|
169
|
+
|
|
170
|
+
locale_data::GRAMMAR[:conjunction]
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
def locale_default_fraction_word(locale_data)
|
|
174
|
+
return locale_data.default_fraction_word if locale_data.respond_to?(:default_fraction_word)
|
|
175
|
+
|
|
176
|
+
locale_data::GRAMMAR[:default_fraction]
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
def locale_decimal_separator_word(locale_data)
|
|
180
|
+
return locale_data.decimal_separator_word if locale_data.respond_to?(:decimal_separator_word)
|
|
181
|
+
|
|
182
|
+
locale_data::GRAMMAR[:decimal_separator]
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
def locale_join_decimal_words(locale_data, words)
|
|
186
|
+
return locale_data.join_decimal_words(words) if locale_data.respond_to?(:join_decimal_words)
|
|
187
|
+
|
|
188
|
+
words.compact.reject(&:empty?).join(" ")
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
def locale_join_fraction_words(locale_data, words)
|
|
192
|
+
return locale_data.join_fraction_words(words) if locale_data.respond_to?(:join_fraction_words)
|
|
193
|
+
|
|
194
|
+
words.reject(&:empty?).join(" ")
|
|
195
|
+
end
|
|
196
|
+
|
|
76
197
|
# n — 0..999, scale_idx — индекс разряда (0 — единицы, 1 — тысячи, ...)
|
|
77
198
|
# feminine: true — использовать женский род для единиц (нужно для тысяч/копеек)
|
|
78
|
-
def triple_to_words(n, scale_idx, local_data, feminine: false)
|
|
199
|
+
def triple_to_words(n, scale_idx, local_data, feminine: false, locale: nil)
|
|
79
200
|
return [] if n.zero?
|
|
201
|
+
|
|
202
|
+
if local_data.respond_to?(:triple_to_words)
|
|
203
|
+
return local_data.triple_to_words(n, scale_idx, feminine: feminine)
|
|
204
|
+
end
|
|
205
|
+
|
|
80
206
|
words = []
|
|
81
207
|
|
|
82
208
|
words << local_data::HUNDREDS[n / 100] if n >= 100
|
|
@@ -90,19 +216,21 @@ module Num2words
|
|
|
90
216
|
words << (feminine ? local_data::ONES_FEM[ones] : local_data::ONES_MASC[ones]) if ones.positive?
|
|
91
217
|
end
|
|
92
218
|
|
|
93
|
-
words <<
|
|
219
|
+
words << locale_pluralize(local_data, n, local_data::SCALES[scale_idx]) unless scale_idx.zero?
|
|
94
220
|
words.compact
|
|
95
221
|
end
|
|
96
222
|
|
|
97
|
-
def to_words_fractional(number, locale, feminine, locale_data, style: :fraction)
|
|
98
|
-
minus_word = locale_data
|
|
99
|
-
conjunction_word = locale_data
|
|
100
|
-
default_fraction = locale_data
|
|
223
|
+
def to_words_fractional(number, locale, feminine, locale_data, style: :fraction, joiner: :default)
|
|
224
|
+
minus_word = locale_minus_word(locale_data)
|
|
225
|
+
conjunction_word = locale_fraction_joiner(locale_data, joiner)
|
|
226
|
+
default_fraction = locale_default_fraction_word(locale_data)
|
|
101
227
|
fractions_data = locale_data::FRACTIONS || {}
|
|
102
228
|
|
|
103
|
-
|
|
229
|
+
negative = number.is_a?(String) ? number.start_with?("-") : number.negative?
|
|
230
|
+
sign_word = negative ? minus_word : ""
|
|
104
231
|
|
|
105
|
-
|
|
232
|
+
absolute_number = number.is_a?(String) ? number.sub(/\A-/, "").tr(",", ".") : number.abs.to_s
|
|
233
|
+
integer_string, fraction_string = absolute_number.split('.', 2)
|
|
106
234
|
integer_value = integer_string.to_i
|
|
107
235
|
|
|
108
236
|
return to_words_integer(integer_value, locale, feminine, locale_data) if fraction_string.to_i.zero?
|
|
@@ -113,23 +241,29 @@ module Num2words
|
|
|
113
241
|
|
|
114
242
|
integer_words = to_words_integer(integer_value, locale, feminine, locale_data)
|
|
115
243
|
|
|
116
|
-
if
|
|
117
|
-
fraction_digits =
|
|
118
|
-
|
|
119
|
-
return full_string
|
|
244
|
+
if style == :decimal && locale_data.respond_to?(:decimal_fraction_words)
|
|
245
|
+
fraction_digits = locale_data.decimal_fraction_words(fraction_string)
|
|
246
|
+
return locale_join_decimal_words(locale_data, [sign_word, integer_words, locale_decimal_separator_word(locale_data), fraction_digits])
|
|
120
247
|
end
|
|
121
248
|
|
|
122
|
-
|
|
249
|
+
numerator_feminine = locale_data.respond_to?(:fraction_numerator_feminine?) ? locale_data.fraction_numerator_feminine? : feminine
|
|
250
|
+
numerator_words = to_words_integer(numerator, locale, numerator_feminine, locale_data)
|
|
123
251
|
|
|
124
252
|
denom_forms = fractions_data[denominator] || fractions_data[denominator.to_s] # массив склонений
|
|
125
|
-
denominator_words = denom_forms.is_a?(Array) ?
|
|
253
|
+
denominator_words = denom_forms.is_a?(Array) ? locale_pluralize(locale_data, numerator, denom_forms) : default_fraction
|
|
126
254
|
|
|
127
|
-
[sign_word, integer_words, conjunction_word, numerator_words, denominator_words]
|
|
255
|
+
locale_join_fraction_words(locale_data, [sign_word, integer_words, conjunction_word, numerator_words, denominator_words])
|
|
128
256
|
end
|
|
129
257
|
|
|
130
258
|
def to_words_integer(number, locale, feminine, locale_data)
|
|
259
|
+
return locale_data.integer_to_words(number, feminine: feminine) if locale_data.respond_to?(:integer_to_words)
|
|
260
|
+
|
|
131
261
|
integer_value = Integer(number)
|
|
132
262
|
|
|
263
|
+
minus_word = locale_minus_word(locale_data)
|
|
264
|
+
negative = integer_value.negative?
|
|
265
|
+
integer_value = integer_value.abs
|
|
266
|
+
|
|
133
267
|
return (feminine ? locale_data::ONES_FEM[0] : locale_data::ONES_MASC[0]) if integer_value.zero?
|
|
134
268
|
|
|
135
269
|
groups = integer_value.to_s
|
|
@@ -139,14 +273,15 @@ module Num2words
|
|
|
139
273
|
words = []
|
|
140
274
|
groups.each_with_index do |group_value, index|
|
|
141
275
|
scale_index = groups.size - index - 1
|
|
142
|
-
group_feminine = (
|
|
143
|
-
words.concat triple_to_words(group_value, scale_index, locale_data, feminine: group_feminine)
|
|
276
|
+
group_feminine = (locale_data.respond_to?(:feminine_group?) && locale_data.feminine_group?(scale_index)) || feminine
|
|
277
|
+
words.concat triple_to_words(group_value, scale_index, locale_data, feminine: group_feminine, locale: locale)
|
|
144
278
|
end
|
|
145
279
|
|
|
280
|
+
words.unshift(minus_word) if negative
|
|
146
281
|
words.join(" ")
|
|
147
282
|
end
|
|
148
283
|
|
|
149
|
-
def to_words_date(date, locale, locale_data, format: :default)
|
|
284
|
+
def to_words_date(date, locale, locale_data, format: :default, date_case: :default)
|
|
150
285
|
date = Date.parse(date.to_s) unless date.is_a?(Date)
|
|
151
286
|
|
|
152
287
|
day, month, year = [date.day, date.month, date.year]
|
|
@@ -159,9 +294,18 @@ module Num2words
|
|
|
159
294
|
raise ArgumentError, "Months not found for locale #{locale}" unless months
|
|
160
295
|
raise ArgumentError, "Template not found for locale #{locale}" unless template
|
|
161
296
|
|
|
162
|
-
|
|
297
|
+
day_gender = date_case.to_sym == :genitive ? :masculine : :neuter
|
|
298
|
+
day_words = if locale_data.respond_to?(:date_day)
|
|
299
|
+
locale_data.date_day(day, format: format, date_case: date_case)
|
|
300
|
+
else
|
|
301
|
+
to_words_ordinal(day, locale, format, locale_data, gender: day_gender)
|
|
302
|
+
end
|
|
163
303
|
month_words = months[month - 1]
|
|
164
|
-
year_words =
|
|
304
|
+
year_words = if locale_data.respond_to?(:date_year)
|
|
305
|
+
locale_data.date_year(year, format: format)
|
|
306
|
+
else
|
|
307
|
+
to_words_ordinal(year, locale, format, locale_data)
|
|
308
|
+
end
|
|
165
309
|
|
|
166
310
|
template % { day: day_words, month: month_words, year: year_words }
|
|
167
311
|
end
|
|
@@ -173,7 +317,7 @@ module Num2words
|
|
|
173
317
|
gender_data = ordinals[gender] || ordinals[:masculine]
|
|
174
318
|
raise ArgumentError, "Gender #{gender} not found for locale #{locale}, format #{format}" unless gender_data
|
|
175
319
|
|
|
176
|
-
return gender_data[value - 1] if gender_data
|
|
320
|
+
return gender_data[value - 1] if value.between?(1, gender_data.length)
|
|
177
321
|
|
|
178
322
|
if value > 31
|
|
179
323
|
thousands = (value / 100) * 100
|
|
@@ -197,18 +341,22 @@ module Num2words
|
|
|
197
341
|
words = locale_data::TIME[:words]
|
|
198
342
|
template = locale_data::TIME_TEMPLATE
|
|
199
343
|
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
344
|
+
hour_feminine = locale_time_unit_feminine?(locale_data, :hour, false)
|
|
345
|
+
minute_feminine = locale_time_unit_feminine?(locale_data, :minute, true)
|
|
346
|
+
second_feminine = locale_time_unit_feminine?(locale_data, :second, true)
|
|
347
|
+
|
|
348
|
+
hours = locale_join_time_words(locale_data,
|
|
349
|
+
locale_time_number_words(locale_data, time.hour, :hour, locale, hour_feminine),
|
|
350
|
+
locale_pluralize(locale_data, time.hour, words[:hour])
|
|
351
|
+
)
|
|
352
|
+
minutes = locale_join_time_words(locale_data,
|
|
353
|
+
locale_time_number_words(locale_data, time.min, :minute, locale, minute_feminine),
|
|
354
|
+
locale_pluralize(locale_data, time.min, words[:minute])
|
|
355
|
+
)
|
|
356
|
+
seconds = locale_join_time_words(locale_data,
|
|
357
|
+
locale_time_number_words(locale_data, time.sec, :second, locale, second_feminine),
|
|
358
|
+
locale_pluralize(locale_data, time.sec, words[:second])
|
|
359
|
+
)
|
|
212
360
|
|
|
213
361
|
format = if short
|
|
214
362
|
time.min.zero? && time.sec.zero? ? :hours_only : :hours_minutes
|
|
@@ -228,13 +376,13 @@ module Num2words
|
|
|
228
376
|
end
|
|
229
377
|
end
|
|
230
378
|
|
|
231
|
-
def to_words_datetime(datetime, locale, locale_data, format: :default, only: nil, short: false)
|
|
379
|
+
def to_words_datetime(datetime, locale, locale_data, format: :default, only: nil, short: false, date_case: :default)
|
|
232
380
|
datetime = DateTime.parse(datetime) if datetime.is_a?(String)
|
|
233
381
|
|
|
234
382
|
date_format = short && only == :date ? :short : format
|
|
235
383
|
time_format = short && only == :time ? :short : :default
|
|
236
384
|
|
|
237
|
-
date_part = to_words_date(datetime.to_date, locale, locale_data, format: date_format)
|
|
385
|
+
date_part = to_words_date(datetime.to_date, locale, locale_data, format: date_format, date_case: date_case)
|
|
238
386
|
time_part = to_words_time(datetime.to_time, locale, locale_data, format: time_format, short: short)
|
|
239
387
|
|
|
240
388
|
return date_part if only == :date
|
|
@@ -260,12 +408,12 @@ module Num2words
|
|
|
260
408
|
case value
|
|
261
409
|
when Integer then :integer
|
|
262
410
|
when Float then :float
|
|
411
|
+
when DateTime then :datetime
|
|
263
412
|
when Date then :date
|
|
264
413
|
when Time then :time
|
|
265
|
-
when DateTime then :datetime
|
|
266
414
|
when String
|
|
267
415
|
return :integer if value.match?(/\A-?\d+\z/)
|
|
268
|
-
return :float if value.match?(/\A-?\d
|
|
416
|
+
return :float if value.match?(/\A-?\d+[\.,]\d+\z/)
|
|
269
417
|
return :time if value.match?(/\A\d{1,2}:\d{2}(:\d{2})?\z/)
|
|
270
418
|
|
|
271
419
|
# Форматы даты
|
data/lib/num2words/locales/ar.rb
CHANGED
|
@@ -20,6 +20,136 @@ module Num2words
|
|
|
20
20
|
DATETIME_TEMPLATE = I18n.t("num2words.datetime.template", locale: :ar)
|
|
21
21
|
|
|
22
22
|
ORDINALS = I18n.t("num2words.numbers.ordinals", locale: :ar)
|
|
23
|
+
|
|
24
|
+
module_function
|
|
25
|
+
|
|
26
|
+
def integer_to_words(number, feminine: false)
|
|
27
|
+
integer_value = Integer(number)
|
|
28
|
+
negative = integer_value.negative?
|
|
29
|
+
integer_value = integer_value.abs
|
|
30
|
+
|
|
31
|
+
return (feminine ? ONES_FEM[0] : ONES_MASC[0]) if integer_value.zero?
|
|
32
|
+
|
|
33
|
+
groups = integer_value.to_s
|
|
34
|
+
.chars.reverse.each_slice(3).map(&:reverse)
|
|
35
|
+
.map(&:join).map!(&:to_i).reverse
|
|
36
|
+
|
|
37
|
+
words = []
|
|
38
|
+
groups.each_with_index do |group_value, index|
|
|
39
|
+
next if group_value.zero?
|
|
40
|
+
|
|
41
|
+
scale_idx = groups.size - index - 1
|
|
42
|
+
words << scale_group_to_words(group_value, scale_idx, feminine: feminine && scale_idx.zero?)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
result = words.join(" و")
|
|
46
|
+
negative ? [minus_word, result].join(" ") : result
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def triple_to_words(number, scale_idx, feminine: false)
|
|
50
|
+
scale_group_to_words(number, scale_idx, feminine: feminine).split
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def scale_group_to_words(number, scale_idx, feminine: false)
|
|
54
|
+
return under_thousand(number, feminine: feminine) if scale_idx.zero?
|
|
55
|
+
|
|
56
|
+
scale_forms = SCALES[scale_idx]
|
|
57
|
+
case number
|
|
58
|
+
when 1
|
|
59
|
+
scale_forms[0]
|
|
60
|
+
when 2
|
|
61
|
+
scale_forms[1]
|
|
62
|
+
else
|
|
63
|
+
scale_form = number.between?(3, 10) ? scale_forms[2] : scale_forms[0]
|
|
64
|
+
[under_thousand(number), scale_form].join(" ")
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def under_thousand(number, feminine: false)
|
|
69
|
+
hundreds = number / 100
|
|
70
|
+
rest = number % 100
|
|
71
|
+
|
|
72
|
+
return under_hundred(rest, feminine: feminine) if hundreds.zero?
|
|
73
|
+
return HUNDREDS[hundreds] if rest.zero?
|
|
74
|
+
|
|
75
|
+
[HUNDREDS[hundreds], under_hundred(rest, feminine: feminine)].join(" و")
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def under_hundred(number, feminine: false)
|
|
79
|
+
ones_data = feminine ? ONES_FEM : ONES_MASC
|
|
80
|
+
|
|
81
|
+
return ones_data[number] if number < 10
|
|
82
|
+
return TEENS[number - 10] if number < 20
|
|
83
|
+
|
|
84
|
+
tens = number / 10
|
|
85
|
+
ones = number % 10
|
|
86
|
+
|
|
87
|
+
return TENS[tens] if ones.zero?
|
|
88
|
+
|
|
89
|
+
[ones_data[ones], TENS[tens]].join(" و")
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def minus_word
|
|
93
|
+
GRAMMAR[:minus]
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def fraction_joiner(joiner)
|
|
97
|
+
joiner.to_sym == :and ? "و" : GRAMMAR[:conjunction]
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def default_fraction_word
|
|
101
|
+
GRAMMAR[:default_fraction]
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def decimal_separator_word
|
|
105
|
+
GRAMMAR[:conjunction]
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def decimal_fraction_words(fraction_string)
|
|
109
|
+
fraction_string.chars.map { |digit| integer_to_words(digit.to_i) }.join(" ")
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def join_fraction_words(words)
|
|
113
|
+
parts = words.reject(&:empty?)
|
|
114
|
+
joiner_index = parts.index("و")
|
|
115
|
+
|
|
116
|
+
if joiner_index && parts[joiner_index + 1]
|
|
117
|
+
parts[joiner_index + 1] = "و#{parts[joiner_index + 1]}"
|
|
118
|
+
parts.delete_at(joiner_index)
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
parts.join(" ")
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def fraction_numerator_feminine?
|
|
125
|
+
false
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
def date_day(day, format:, date_case:)
|
|
129
|
+
ordinal(day, format)
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
def date_year(year, format:)
|
|
133
|
+
integer_to_words(year)
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
def ordinal(value, format, gender: :masculine)
|
|
137
|
+
ordinals = ORDINALS[format] || ORDINALS[:default]
|
|
138
|
+
gender_data = ordinals[gender] || ordinals[:masculine]
|
|
139
|
+
|
|
140
|
+
return gender_data[value - 1] if value.between?(1, gender_data.length)
|
|
141
|
+
|
|
142
|
+
integer_to_words(value)
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
def pluralize(number, singular, dual, plural)
|
|
146
|
+
number = number.abs
|
|
147
|
+
return singular if number == 1
|
|
148
|
+
return dual if number == 2
|
|
149
|
+
return plural if number.zero? || number.between?(3, 10)
|
|
150
|
+
|
|
151
|
+
singular
|
|
152
|
+
end
|
|
23
153
|
end
|
|
24
154
|
|
|
25
155
|
register :ar, AR
|
data/lib/num2words/locales/be.rb
CHANGED
|
@@ -20,6 +20,107 @@ module Num2words
|
|
|
20
20
|
DATETIME_TEMPLATE = I18n.t("num2words.datetime.template", locale: :be)
|
|
21
21
|
|
|
22
22
|
ORDINALS = I18n.t("num2words.numbers.ordinals", locale: :be)
|
|
23
|
+
|
|
24
|
+
module_function
|
|
25
|
+
|
|
26
|
+
def feminine_group?(scale_idx)
|
|
27
|
+
scale_idx == 1
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def fraction_numerator_feminine?
|
|
31
|
+
true
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def minus_word
|
|
35
|
+
GRAMMAR[:minus]
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def fraction_joiner(joiner)
|
|
39
|
+
joiner.to_sym == :and ? "і" : GRAMMAR[:conjunction]
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def default_fraction_word
|
|
43
|
+
GRAMMAR[:default_fraction]
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def triple_to_words(number, scale_idx, feminine: false)
|
|
47
|
+
words = []
|
|
48
|
+
|
|
49
|
+
words << HUNDREDS[number / 100] if number >= 100
|
|
50
|
+
rest = number % 100
|
|
51
|
+
|
|
52
|
+
if rest.between?(10, 19)
|
|
53
|
+
words << TEENS[rest - 10]
|
|
54
|
+
else
|
|
55
|
+
words << TENS[rest / 10] if rest >= 20
|
|
56
|
+
ones = rest % 10
|
|
57
|
+
words << (feminine ? ONES_FEM[ones] : ONES_MASC[ones]) if ones.positive?
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
words << pluralize(number, *SCALES[scale_idx]) unless scale_idx.zero?
|
|
61
|
+
words.compact
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def date_day(day, format:, date_case:)
|
|
65
|
+
return ordinal(day, :default, gender: :masculine) if date_case.to_sym == :genitive
|
|
66
|
+
|
|
67
|
+
ordinal(day, :nominative, gender: :neuter)
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def date_year(year, format:)
|
|
71
|
+
ordinal(year, :default)
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def ordinal(value, format, gender: :masculine)
|
|
75
|
+
ordinals = ORDINALS[format]
|
|
76
|
+
gender_data = ordinals[gender] || ordinals[:masculine]
|
|
77
|
+
|
|
78
|
+
return gender_data[value - 1] if value.between?(1, gender_data.length)
|
|
79
|
+
|
|
80
|
+
if value > 31
|
|
81
|
+
thousands = (value / 100) * 100
|
|
82
|
+
last_two = value % 100
|
|
83
|
+
base_year = cardinal(thousands)
|
|
84
|
+
last_ordinal = gender_data[last_two - 1] || cardinal(last_two)
|
|
85
|
+
|
|
86
|
+
return [base_year, last_ordinal].join(" ")
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
cardinal(value)
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def cardinal(number, feminine: false)
|
|
93
|
+
integer_value = Integer(number)
|
|
94
|
+
negative = integer_value.negative?
|
|
95
|
+
integer_value = integer_value.abs
|
|
96
|
+
|
|
97
|
+
return (feminine ? ONES_FEM[0] : ONES_MASC[0]) if integer_value.zero?
|
|
98
|
+
|
|
99
|
+
groups = integer_value.to_s
|
|
100
|
+
.chars.reverse.each_slice(3).map(&:reverse)
|
|
101
|
+
.map(&:join).map!(&:to_i).reverse
|
|
102
|
+
|
|
103
|
+
words = []
|
|
104
|
+
groups.each_with_index do |group_value, index|
|
|
105
|
+
scale_idx = groups.size - index - 1
|
|
106
|
+
group_feminine = feminine_group?(scale_idx) || feminine
|
|
107
|
+
words.concat triple_to_words(group_value, scale_idx, feminine: group_feminine)
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
words.unshift(minus_word) if negative
|
|
111
|
+
words.join(" ")
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def pluralize(number, singular, few, plural)
|
|
115
|
+
number = number.abs
|
|
116
|
+
return plural if (11..14).include?(number % 100)
|
|
117
|
+
|
|
118
|
+
case number % 10
|
|
119
|
+
when 1 then singular
|
|
120
|
+
when 2..4 then few
|
|
121
|
+
else plural
|
|
122
|
+
end
|
|
123
|
+
end
|
|
23
124
|
end
|
|
24
125
|
|
|
25
126
|
register :be, BE
|