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.
Files changed (111) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +65 -0
  3. data/README.md +145 -135
  4. data/bin/console +2 -14
  5. data/config/locales/ar.yml +5 -1
  6. data/config/locales/be.yml +4 -0
  7. data/config/locales/bg.yml +4 -0
  8. data/config/locales/bn.yml +4 -0
  9. data/config/locales/cs.yml +6 -2
  10. data/config/locales/da.yml +4 -0
  11. data/config/locales/de.yml +8 -4
  12. data/config/locales/el.yml +4 -0
  13. data/config/locales/en.yml +4 -0
  14. data/config/locales/es.yml +4 -0
  15. data/config/locales/et.yml +9 -5
  16. data/config/locales/fa.yml +15 -11
  17. data/config/locales/fi.yml +8 -4
  18. data/config/locales/fr.yml +4 -0
  19. data/config/locales/gu.yml +4 -0
  20. data/config/locales/he.yml +6 -2
  21. data/config/locales/hi.yml +4 -0
  22. data/config/locales/hr.yml +8 -4
  23. data/config/locales/hu.yml +6 -2
  24. data/config/locales/id.yml +4 -0
  25. data/config/locales/it.yml +4 -0
  26. data/config/locales/ja.yml +10 -5
  27. data/config/locales/kn.yml +186 -10
  28. data/config/locales/ko.yml +184 -13
  29. data/config/locales/kz.yml +183 -17
  30. data/config/locales/lt.yml +184 -8
  31. data/config/locales/lv.yml +188 -12
  32. data/config/locales/ml.yml +189 -13
  33. data/config/locales/mr.yml +184 -8
  34. data/config/locales/ms.yml +184 -8
  35. data/config/locales/nb.yml +183 -17
  36. data/config/locales/nl.yml +184 -13
  37. data/config/locales/pa.yml +189 -13
  38. data/config/locales/pl.yml +184 -13
  39. data/config/locales/pt.yml +184 -13
  40. data/config/locales/ro.yml +183 -12
  41. data/config/locales/ru.yml +8 -0
  42. data/config/locales/sk.yml +184 -8
  43. data/config/locales/sl.yml +185 -9
  44. data/config/locales/sr.yml +186 -15
  45. data/config/locales/sv.yml +182 -13
  46. data/config/locales/sw.yml +184 -8
  47. data/config/locales/ta.yml +184 -8
  48. data/config/locales/te.yml +185 -9
  49. data/config/locales/th.yml +183 -12
  50. data/config/locales/tr.yml +183 -12
  51. data/config/locales/uk.yml +189 -18
  52. data/config/locales/ur.yml +187 -11
  53. data/config/locales/vi.yml +183 -12
  54. data/config/locales/zh.yml +191 -9
  55. data/exe/num2words-console +7 -0
  56. data/lib/num2words/console.rb +16 -0
  57. data/lib/num2words/converter.rb +195 -47
  58. data/lib/num2words/locales/ar.rb +130 -0
  59. data/lib/num2words/locales/be.rb +101 -0
  60. data/lib/num2words/locales/bg.rb +127 -0
  61. data/lib/num2words/locales/bn.rb +112 -0
  62. data/lib/num2words/locales/cs.rb +106 -0
  63. data/lib/num2words/locales/da.rb +97 -0
  64. data/lib/num2words/locales/de.rb +119 -0
  65. data/lib/num2words/locales/el.rb +143 -0
  66. data/lib/num2words/locales/en.rb +90 -0
  67. data/lib/num2words/locales/es.rb +185 -0
  68. data/lib/num2words/locales/et.rb +104 -0
  69. data/lib/num2words/locales/fa.rb +104 -0
  70. data/lib/num2words/locales/fi.rb +104 -0
  71. data/lib/num2words/locales/fr.rb +121 -0
  72. data/lib/num2words/locales/gu.rb +112 -0
  73. data/lib/num2words/locales/he.rb +130 -0
  74. data/lib/num2words/locales/hi.rb +112 -0
  75. data/lib/num2words/locales/hr.rb +117 -0
  76. data/lib/num2words/locales/hu.rb +111 -0
  77. data/lib/num2words/locales/id.rb +104 -0
  78. data/lib/num2words/locales/it.rb +142 -0
  79. data/lib/num2words/locales/ja.rb +126 -0
  80. data/lib/num2words/locales/kn.rb +122 -2
  81. data/lib/num2words/locales/ko.rb +158 -2
  82. data/lib/num2words/locales/kz.rb +137 -2
  83. data/lib/num2words/locales/lt.rb +146 -2
  84. data/lib/num2words/locales/lv.rb +148 -2
  85. data/lib/num2words/locales/ml.rb +142 -2
  86. data/lib/num2words/locales/mr.rb +142 -2
  87. data/lib/num2words/locales/ms.rb +134 -2
  88. data/lib/num2words/locales/nb.rb +153 -0
  89. data/lib/num2words/locales/nl.rb +142 -2
  90. data/lib/num2words/locales/pa.rb +122 -2
  91. data/lib/num2words/locales/pl.rb +149 -2
  92. data/lib/num2words/locales/pt.rb +164 -2
  93. data/lib/num2words/locales/ro.rb +165 -2
  94. data/lib/num2words/locales/ru.rb +100 -0
  95. data/lib/num2words/locales/sk.rb +124 -2
  96. data/lib/num2words/locales/sl.rb +154 -2
  97. data/lib/num2words/locales/sr.rb +141 -3
  98. data/lib/num2words/locales/sv.rb +141 -2
  99. data/lib/num2words/locales/sw.rb +133 -2
  100. data/lib/num2words/locales/ta.rb +142 -2
  101. data/lib/num2words/locales/te.rb +142 -2
  102. data/lib/num2words/locales/th.rb +133 -2
  103. data/lib/num2words/locales/tr.rb +141 -2
  104. data/lib/num2words/locales/uk.rb +134 -2
  105. data/lib/num2words/locales/ur.rb +130 -2
  106. data/lib/num2words/locales/vi.rb +142 -2
  107. data/lib/num2words/locales/zh.rb +172 -2
  108. data/lib/num2words/version.rb +1 -1
  109. data/num2words.gemspec +3 -3
  110. metadata +13 -10
  111. data/lib/num2words/locales/no.rb +0 -19
@@ -20,6 +20,149 @@ module Num2words
20
20
  DATETIME_TEMPLATE = I18n.t("num2words.datetime.template", locale: :el)
21
21
 
22
22
  ORDINALS = I18n.t("num2words.numbers.ordinals", locale: :el)
23
+
24
+ ONES_NEUT = ["μηδέν", "ένα", "δύο", "τρία", "τέσσερα", "πέντε", "έξι", "επτά", "οκτώ", "εννέα"].freeze
25
+ TEENS_MASC = ["δέκα", "έντεκα", "δώδεκα", "δεκατρείς", "δεκατέσσερις", "δεκαπέντε", "δεκαέξι", "δεκαεπτά", "δεκαοκτώ", "δεκαεννέα"].freeze
26
+ TEENS_FEM = TEENS_MASC
27
+ TEENS_NEUT = TEENS
28
+ FEMININE_MAJOR_CURRENCIES = %i[GBP TRY UAH CZK SEK NOK DKK INR IDR PKR].freeze
29
+ NEUTER_MAJOR_CURRENCIES = %i[EUR USD RUB KRW PLN RON BGN BYN KZT ILS HUF SAR THB VND IRR RSD MYR BDT KES BRL CNY JPY].freeze
30
+ NEUTER_MINOR_CURRENCIES = %i[EUR USD GBP JPY CNY RUB KRW TRY PLN UAH CZK RON BGN BYN KZT SEK NOK DKK HUF THB VND IDR IRR MYR KES BRL].freeze
31
+
32
+ module_function
33
+
34
+ def integer_to_words(number, feminine: false)
35
+ cardinal(number, gender: feminine ? :feminine : :masculine)
36
+ end
37
+
38
+ def triple_to_words(number, scale_idx, feminine: false)
39
+ gender = feminine ? :feminine : :masculine
40
+ gender = :feminine if scale_idx == 1
41
+ gender = :neuter if scale_idx > 1
42
+ triple_words(number, scale_idx, gender: gender)
43
+ end
44
+
45
+ def minus_word
46
+ GRAMMAR[:minus]
47
+ end
48
+
49
+ def fraction_joiner(joiner)
50
+ joiner.to_sym == :and ? "και" : GRAMMAR[:conjunction]
51
+ end
52
+
53
+ def default_fraction_word
54
+ GRAMMAR[:default_fraction]
55
+ end
56
+
57
+ def fraction_numerator_feminine?
58
+ false
59
+ end
60
+
61
+ def time_unit_feminine?(unit)
62
+ unit == :hour
63
+ end
64
+
65
+ def currency_number_words(number, currency, unit:)
66
+ gender = unit == :major ? currency_major_gender(currency) : currency_minor_gender(currency)
67
+ cardinal(number, gender: gender)
68
+ end
69
+
70
+ def date_day(day, format:, date_case:)
71
+ ordinal(day, :default)
72
+ end
73
+
74
+ def date_year(year, format:)
75
+ cardinal(year, gender: :neuter)
76
+ end
77
+
78
+ def ordinal(value, format, gender: :masculine)
79
+ ordinals = ORDINALS[format] || ORDINALS[:nominative]
80
+ gender_data = ordinals[gender] || ordinals[:masculine]
81
+
82
+ return gender_data[value - 1] if value.between?(1, gender_data.length)
83
+
84
+ cardinal(value)
85
+ end
86
+
87
+ def cardinal(number, gender: :masculine)
88
+ integer_value = Integer(number)
89
+ negative = integer_value.negative?
90
+ integer_value = integer_value.abs
91
+
92
+ return ones(gender)[0] if integer_value.zero?
93
+
94
+ groups = integer_value.to_s
95
+ .chars.reverse.each_slice(3).map(&:reverse)
96
+ .map(&:join).map!(&:to_i).reverse
97
+
98
+ words = []
99
+ groups.each_with_index do |group_value, index|
100
+ next if group_value.zero?
101
+
102
+ scale_idx = groups.size - index - 1
103
+ group_gender = scale_idx.zero? ? gender : scale_gender(scale_idx)
104
+ group_words = triple_words(group_value, scale_idx, gender: group_gender)
105
+ group_words.shift if scale_idx == 1 && group_value == 1
106
+ words.concat group_words
107
+ end
108
+
109
+ words.unshift(minus_word) if negative
110
+ words.join(" ")
111
+ end
112
+
113
+ def triple_words(number, scale_idx, gender:)
114
+ return [] if number.zero?
115
+
116
+ words = []
117
+ words << HUNDREDS[number / 100] if number >= 100
118
+ rest = number % 100
119
+
120
+ if rest.between?(10, 19)
121
+ words << teens(gender)[rest - 10]
122
+ else
123
+ words << TENS[rest / 10] if rest >= 20
124
+ value = rest % 10
125
+ words << ones(gender)[value] if value.positive?
126
+ end
127
+
128
+ words << pluralize(number, *SCALES[scale_idx]) unless scale_idx.zero?
129
+ words.compact
130
+ end
131
+
132
+ def scale_gender(scale_idx)
133
+ scale_idx == 1 ? :feminine : :neuter
134
+ end
135
+
136
+ def ones(gender)
137
+ case gender
138
+ when :feminine then ONES_FEM
139
+ when :neuter then ONES_NEUT
140
+ else ONES_MASC
141
+ end
142
+ end
143
+
144
+ def teens(gender)
145
+ case gender
146
+ when :feminine then TEENS_FEM
147
+ when :neuter then TEENS_NEUT
148
+ else TEENS_MASC
149
+ end
150
+ end
151
+
152
+ def currency_major_gender(currency)
153
+ return :feminine if FEMININE_MAJOR_CURRENCIES.include?(currency)
154
+ return :neuter if NEUTER_MAJOR_CURRENCIES.include?(currency)
155
+
156
+ :masculine
157
+ end
158
+
159
+ def currency_minor_gender(currency)
160
+ NEUTER_MINOR_CURRENCIES.include?(currency) ? :neuter : :feminine
161
+ end
162
+
163
+ def pluralize(number, singular, few, plural)
164
+ number.abs == 1 ? singular : few
165
+ end
23
166
  end
24
167
 
25
168
  register :el, EL
@@ -20,6 +20,96 @@ module Num2words
20
20
  DATETIME_TEMPLATE = I18n.t("num2words.datetime.template", locale: :en)
21
21
 
22
22
  ORDINALS = I18n.t("num2words.numbers.ordinals", locale: :en)
23
+
24
+ module_function
25
+
26
+ def feminine_group?(_scale_idx)
27
+ false
28
+ end
29
+
30
+ def fraction_numerator_feminine?
31
+ false
32
+ end
33
+
34
+ def minus_word
35
+ GRAMMAR[:minus]
36
+ end
37
+
38
+ def fraction_joiner(_joiner)
39
+ GRAMMAR[:conjunction]
40
+ end
41
+
42
+ def default_fraction_word
43
+ GRAMMAR[:default_fraction]
44
+ end
45
+
46
+ def decimal_separator_word
47
+ "point"
48
+ end
49
+
50
+ def decimal_fraction_words(fraction_string)
51
+ fraction_string.chars.map { |digit| cardinal(digit.to_i) }.join(" ")
52
+ end
53
+
54
+ def triple_to_words(number, scale_idx, feminine: false)
55
+ words = []
56
+
57
+ words << HUNDREDS[number / 100] if number >= 100
58
+ rest = number % 100
59
+
60
+ if rest.between?(10, 19)
61
+ words << TEENS[rest - 10]
62
+ else
63
+ words << TENS[rest / 10] if rest >= 20
64
+ ones = rest % 10
65
+ words << (feminine ? ONES_FEM[ones] : ONES_MASC[ones]) if ones.positive?
66
+ end
67
+
68
+ words << pluralize(number, *SCALES[scale_idx]) unless scale_idx.zero?
69
+ words.compact
70
+ end
71
+
72
+ def date_day(day, format:, date_case:)
73
+ ordinal(day, format)
74
+ end
75
+
76
+ def date_year(year, format:)
77
+ cardinal(year)
78
+ end
79
+
80
+ def ordinal(value, format, gender: :masculine)
81
+ ordinals = ORDINALS[format]
82
+ gender_data = ordinals[gender] || ordinals[:masculine]
83
+
84
+ return gender_data[value - 1] if value.between?(1, gender_data.length)
85
+
86
+ cardinal(value)
87
+ end
88
+
89
+ def cardinal(number, feminine: false)
90
+ integer_value = Integer(number)
91
+ negative = integer_value.negative?
92
+ integer_value = integer_value.abs
93
+
94
+ return (feminine ? ONES_FEM[0] : ONES_MASC[0]) if integer_value.zero?
95
+
96
+ groups = integer_value.to_s
97
+ .chars.reverse.each_slice(3).map(&:reverse)
98
+ .map(&:join).map!(&:to_i).reverse
99
+
100
+ words = []
101
+ groups.each_with_index do |group_value, index|
102
+ scale_idx = groups.size - index - 1
103
+ words.concat triple_to_words(group_value, scale_idx, feminine: feminine)
104
+ end
105
+
106
+ words.unshift(minus_word) if negative
107
+ words.join(" ")
108
+ end
109
+
110
+ def pluralize(number, singular, few, plural)
111
+ number.abs == 1 ? singular : plural
112
+ end
23
113
  end
24
114
 
25
115
  register :en, EN
@@ -20,6 +20,191 @@ module Num2words
20
20
  DATETIME_TEMPLATE = I18n.t("num2words.datetime.template", locale: :es)
21
21
 
22
22
  ORDINALS = I18n.t("num2words.numbers.ordinals", locale: :es)
23
+
24
+ HUNDREDS_FEM = ["", "cien", "doscientas", "trescientas", "cuatrocientas", "quinientas", "seiscientas", "setecientas", "ochocientas", "novecientas"].freeze
25
+ VEINTI = {
26
+ 21 => { masculine: "veintiuno", feminine: "veintiuna", apocopated: "veintiún" },
27
+ 22 => "veintidós",
28
+ 23 => "veintitrés",
29
+ 24 => "veinticuatro",
30
+ 25 => "veinticinco",
31
+ 26 => "veintiséis",
32
+ 27 => "veintisiete",
33
+ 28 => "veintiocho",
34
+ 29 => "veintinueve"
35
+ }.freeze
36
+ FEMININE_MAJOR_CURRENCIES = %i[GBP TRY UAH CZK SEK NOK DKK INR IDR PKR].freeze
37
+ FEMININE_MINOR_CURRENCIES = %i[BGN ILS SAR].freeze
38
+
39
+ module_function
40
+
41
+ def integer_to_words(number, feminine: false)
42
+ cardinal(number, gender: feminine ? :feminine : :masculine)
43
+ end
44
+
45
+ def triple_to_words(number, scale_idx, feminine: false)
46
+ return [] if number.zero?
47
+
48
+ gender = feminine ? :feminine : :masculine
49
+ words = []
50
+
51
+ case scale_idx
52
+ when 0
53
+ words << under_thousand(number, gender: gender)
54
+ when 1
55
+ words << under_thousand(number, gender: :masculine, apocopate: true) unless number == 1
56
+ words << SCALES[1][0]
57
+ when 2, 4
58
+ words << (number == 1 ? "un" : under_thousand(number, gender: :masculine, apocopate: true))
59
+ words << pluralize(number, *SCALES[scale_idx])
60
+ when 3
61
+ words << under_thousand(number, gender: :masculine, apocopate: true) unless number == 1
62
+ words << SCALES[3][0]
63
+ else
64
+ words << under_thousand(number, gender: :masculine, apocopate: true)
65
+ words << pluralize(number, *SCALES[scale_idx])
66
+ end
67
+
68
+ words
69
+ end
70
+
71
+ def minus_word
72
+ GRAMMAR[:minus]
73
+ end
74
+
75
+ def fraction_joiner(joiner)
76
+ joiner.to_sym == :and ? "y" : GRAMMAR[:conjunction]
77
+ end
78
+
79
+ def default_fraction_word
80
+ GRAMMAR[:default_fraction]
81
+ end
82
+
83
+ def fraction_numerator_feminine?
84
+ true
85
+ end
86
+
87
+ def decimal_separator_word
88
+ "coma"
89
+ end
90
+
91
+ def decimal_fraction_words(fraction_string)
92
+ fraction_string.chars.map { |digit| cardinal(digit.to_i) }.join(" ")
93
+ end
94
+
95
+ def time_unit_feminine?(unit)
96
+ unit == :hour
97
+ end
98
+
99
+ def time_number_words(number, unit:)
100
+ gender = unit == :hour ? :feminine : :masculine
101
+
102
+ cardinal(number, gender: gender, apocopate: gender == :masculine)
103
+ end
104
+
105
+ def currency_number_words(number, currency, unit:)
106
+ gender = if unit == :major
107
+ FEMININE_MAJOR_CURRENCIES.include?(currency) ? :feminine : :masculine
108
+ else
109
+ FEMININE_MINOR_CURRENCIES.include?(currency) ? :feminine : :masculine
110
+ end
111
+
112
+ cardinal(number, gender: gender, apocopate: gender == :masculine)
113
+ end
114
+
115
+ def date_day(day, format:, date_case:)
116
+ day == 1 ? "primero" : cardinal(day)
117
+ end
118
+
119
+ def date_year(year, format:)
120
+ cardinal(year)
121
+ end
122
+
123
+ def ordinal(value, format, gender: :masculine)
124
+ ordinals = ORDINALS[format] || ORDINALS[:nominative]
125
+ gender_data = ordinals[gender] || ordinals[:masculine]
126
+
127
+ return gender_data[value - 1] if value.between?(1, gender_data.length)
128
+
129
+ cardinal(value, gender: gender)
130
+ end
131
+
132
+ def cardinal(number, gender: :masculine, apocopate: false)
133
+ integer_value = Integer(number)
134
+ negative = integer_value.negative?
135
+ integer_value = integer_value.abs
136
+
137
+ return ONES_MASC[0] if integer_value.zero?
138
+
139
+ groups = integer_value.to_s
140
+ .chars.reverse.each_slice(3).map(&:reverse)
141
+ .map(&:join).map!(&:to_i).reverse
142
+
143
+ words = []
144
+ groups.each_with_index do |group_value, index|
145
+ next if group_value.zero?
146
+
147
+ scale_idx = groups.size - index - 1
148
+ words.concat triple_to_words(group_value, scale_idx, feminine: gender == :feminine)
149
+ end
150
+
151
+ words.unshift(minus_word) if negative
152
+ result = words.join(" ")
153
+ apocopate ? apocopate_one(result) : result
154
+ end
155
+
156
+ def under_thousand(number, gender: :masculine, apocopate: false)
157
+ return under_hundred(number, gender: gender, apocopate: apocopate) if number < 100
158
+ return "cien" if number == 100
159
+
160
+ hundreds = number / 100
161
+ rest = number % 100
162
+ hundreds_words = hundreds == 1 ? "ciento" : hundreds_data(gender)[hundreds]
163
+
164
+ return hundreds_words if rest.zero?
165
+
166
+ [hundreds_words, under_hundred(rest, gender: gender, apocopate: apocopate)].join(" ")
167
+ end
168
+
169
+ def under_hundred(number, gender: :masculine, apocopate: false)
170
+ return one_word(number, gender: gender, apocopate: apocopate) if number < 10
171
+ return TEENS[number - 10] if number < 20
172
+ return TENS[2] if number == 20
173
+
174
+ if number < 30
175
+ value = VEINTI[number]
176
+ return value unless value.is_a?(Hash)
177
+
178
+ return value[:apocopated] if apocopate
179
+
180
+ value[gender] || value[:masculine]
181
+ else
182
+ tens = number / 10
183
+ ones = number % 10
184
+ return TENS[tens] if ones.zero?
185
+
186
+ [TENS[tens], one_word(ones, gender: gender, apocopate: apocopate)].join(" y ")
187
+ end
188
+ end
189
+
190
+ def one_word(number, gender:, apocopate:)
191
+ return "un" if number == 1 && apocopate
192
+ return ONES_FEM[number] if gender == :feminine
193
+
194
+ ONES_MASC[number]
195
+ end
196
+
197
+ def apocopate_one(words)
198
+ words.sub(/veintiuno\z/, "veintiún").sub(/ y uno\z/, " y un").sub(/uno\z/, "un")
199
+ end
200
+
201
+ def hundreds_data(gender)
202
+ gender == :feminine ? HUNDREDS_FEM : HUNDREDS
203
+ end
204
+
205
+ def pluralize(number, singular, _few, plural)
206
+ number.abs == 1 ? singular : plural
207
+ end
23
208
  end
24
209
 
25
210
  register :es, ES
@@ -20,6 +20,110 @@ module Num2words
20
20
  DATETIME_TEMPLATE = I18n.t("num2words.datetime.template", locale: :et)
21
21
 
22
22
  ORDINALS = I18n.t("num2words.numbers.ordinals", locale: :et)
23
+
24
+ module_function
25
+
26
+ def integer_to_words(number, feminine: false)
27
+ cardinal(number)
28
+ end
29
+
30
+ def triple_to_words(number, scale_idx, feminine: false)
31
+ return [] if number.zero?
32
+
33
+ words = []
34
+ words << under_thousand(number) unless scale_idx == 1 && number == 1
35
+ words << pluralize(number, *SCALES[scale_idx]) unless scale_idx.zero?
36
+ words
37
+ end
38
+
39
+ def minus_word
40
+ GRAMMAR[:minus]
41
+ end
42
+
43
+ def fraction_joiner(_joiner)
44
+ 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
+ "koma"
57
+ end
58
+
59
+ def decimal_fraction_words(fraction_string)
60
+ fraction_string.chars.map { |digit| cardinal(digit.to_i) }.join(" ")
61
+ end
62
+
63
+ def date_day(day, format:, date_case:)
64
+ ordinal(day, :default)
65
+ end
66
+
67
+ def date_year(year, format:)
68
+ cardinal(year)
69
+ end
70
+
71
+ def ordinal(value, format, gender: :masculine)
72
+ ordinals = ORDINALS[format] || ORDINALS[:default] || ORDINALS[:nominative]
73
+ gender_data = ordinals[gender] || ordinals[:masculine]
74
+
75
+ return gender_data[value - 1] if value.between?(1, gender_data.length)
76
+
77
+ cardinal(value)
78
+ end
79
+
80
+ def cardinal(number)
81
+ integer_value = Integer(number)
82
+ negative = integer_value.negative?
83
+ integer_value = integer_value.abs
84
+
85
+ return ONES_MASC[0] if integer_value.zero?
86
+
87
+ groups = integer_value.to_s
88
+ .chars.reverse.each_slice(3).map(&:reverse)
89
+ .map(&:join).map!(&:to_i).reverse
90
+
91
+ words = []
92
+ groups.each_with_index do |group_value, index|
93
+ next if group_value.zero?
94
+
95
+ scale_idx = groups.size - index - 1
96
+ words.concat triple_to_words(group_value, scale_idx)
97
+ end
98
+
99
+ words.unshift(minus_word) if negative
100
+ words.join(" ")
101
+ end
102
+
103
+ def under_thousand(number)
104
+ return under_hundred(number) if number < 100
105
+
106
+ hundreds = number / 100
107
+ rest = number % 100
108
+ words = [HUNDREDS[hundreds]]
109
+ words << under_hundred(rest) if rest.positive?
110
+ words.join(" ")
111
+ end
112
+
113
+ def under_hundred(number)
114
+ return ONES_MASC[number] if number < 10
115
+ return TEENS[number - 10] if number < 20
116
+
117
+ tens = number / 10
118
+ ones = number % 10
119
+ words = [TENS[tens]]
120
+ words << ONES_MASC[ones] if ones.positive?
121
+ words.join(" ")
122
+ end
123
+
124
+ def pluralize(number, singular, _few, plural)
125
+ number.abs == 1 ? singular : plural
126
+ end
23
127
  end
24
128
 
25
129
  register :et, ET
@@ -20,6 +20,110 @@ module Num2words
20
20
  DATETIME_TEMPLATE = I18n.t("num2words.datetime.template", locale: :fa)
21
21
 
22
22
  ORDINALS = I18n.t("num2words.numbers.ordinals", locale: :fa)
23
+
24
+ module_function
25
+
26
+ def integer_to_words(number, feminine: false)
27
+ cardinal(number)
28
+ end
29
+
30
+ def triple_to_words(number, scale_idx, feminine: false)
31
+ return [] if number.zero?
32
+
33
+ words = []
34
+ words << under_thousand(number) unless scale_idx == 1 && number == 1
35
+ words << SCALES[scale_idx][0] unless scale_idx.zero?
36
+ words
37
+ end
38
+
39
+ def minus_word
40
+ GRAMMAR[:minus]
41
+ end
42
+
43
+ def fraction_joiner(_joiner)
44
+ 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
+ "ممیز"
57
+ end
58
+
59
+ def decimal_fraction_words(fraction_string)
60
+ fraction_string.chars.map { |digit| cardinal(digit.to_i) }.join(" ")
61
+ end
62
+
63
+ def date_day(day, format:, date_case:)
64
+ ordinal(day, :default)
65
+ end
66
+
67
+ def date_year(year, format:)
68
+ cardinal(year)
69
+ end
70
+
71
+ def ordinal(value, format, gender: :masculine)
72
+ ordinals = ORDINALS[format] || ORDINALS[:default] || ORDINALS[:nominative]
73
+ gender_data = ordinals[gender] || ordinals[:masculine]
74
+
75
+ return gender_data[value - 1] if value.between?(1, gender_data.length)
76
+
77
+ cardinal(value)
78
+ end
79
+
80
+ def cardinal(number)
81
+ integer_value = Integer(number)
82
+ negative = integer_value.negative?
83
+ integer_value = integer_value.abs
84
+
85
+ return ONES_MASC[0] if integer_value.zero?
86
+
87
+ groups = integer_value.to_s
88
+ .chars.reverse.each_slice(3).map(&:reverse)
89
+ .map(&:join).map!(&:to_i).reverse
90
+
91
+ words = []
92
+ groups.each_with_index do |group_value, index|
93
+ next if group_value.zero?
94
+
95
+ scale_idx = groups.size - index - 1
96
+ words.concat triple_to_words(group_value, scale_idx)
97
+ end
98
+
99
+ words.unshift(minus_word) if negative
100
+ words.join(" ")
101
+ end
102
+
103
+ def under_thousand(number)
104
+ return under_hundred(number) if number < 100
105
+
106
+ hundreds = number / 100
107
+ rest = number % 100
108
+ words = [HUNDREDS[hundreds]]
109
+ words << under_hundred(rest) if rest.positive?
110
+ words.join(" و ")
111
+ end
112
+
113
+ def under_hundred(number)
114
+ return ONES_MASC[number] if number < 10
115
+ return TEENS[number - 10] if number < 20
116
+
117
+ tens = number / 10
118
+ ones = number % 10
119
+ words = [TENS[tens]]
120
+ words << ONES_MASC[ones] if ones.positive?
121
+ words.join(" و ")
122
+ end
123
+
124
+ def pluralize(number, singular, few, _plural)
125
+ number.abs == 1 ? singular : few
126
+ end
23
127
  end
24
128
 
25
129
  register :fa, FA