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
@@ -10,8 +10,140 @@ module Num2words
10
10
  HUNDREDS = I18n.t("num2words.hundreds", locale: :uk)
11
11
  SCALES = I18n.t("num2words.scales", locale: :uk)
12
12
 
13
- MAJOR_UNIT = I18n.t("num2words.currencies.UAH.name", locale: :uk)
14
- MINOR_UNIT = I18n.t("num2words.currencies.UAH.fractional.name", locale: :uk)
13
+ FRACTIONS = I18n.t("num2words.fractions", locale: :uk)
14
+ GRAMMAR = I18n.t("num2words.grammar", locale: :uk)
15
+
16
+ DATE = I18n.t("num2words.date", locale: :uk)
17
+ DATE_TEMPLATE = I18n.t("num2words.date.template", locale: :uk)
18
+ TIME = I18n.t("num2words.time", locale: :uk)
19
+ TIME_TEMPLATE = I18n.t("num2words.time.template", locale: :uk)
20
+ DATETIME_TEMPLATE = I18n.t("num2words.datetime.template", locale: :uk)
21
+
22
+ ORDINALS = I18n.t("num2words.numbers.ordinals", locale: :uk)
23
+
24
+ FEMININE_MAJOR_CURRENCIES = %i[UAH JPY CZK BGN INR IDR PKR TRY BDT KRW].freeze
25
+ FEMININE_MINOR_CURRENCIES = %i[UAH RUB BGN ILS INR IRR RSD BDT PKR].freeze
26
+
27
+ module_function
28
+
29
+ def feminine_group?(scale_idx)
30
+ scale_idx == 1
31
+ end
32
+
33
+ def fraction_numerator_feminine?
34
+ true
35
+ end
36
+
37
+ def minus_word
38
+ GRAMMAR[:minus]
39
+ end
40
+
41
+ def fraction_joiner(joiner)
42
+ joiner.to_sym == :and ? "і" : GRAMMAR[:conjunction]
43
+ end
44
+
45
+ def default_fraction_word
46
+ GRAMMAR[:default_fraction]
47
+ end
48
+
49
+ def triple_to_words(number, scale_idx, feminine: false)
50
+ words = []
51
+
52
+ words << HUNDREDS[number / 100] if number >= 100
53
+ rest = number % 100
54
+
55
+ if rest.between?(10, 19)
56
+ words << TEENS[rest - 10]
57
+ else
58
+ words << TENS[rest / 10] if rest >= 20
59
+ ones = rest % 10
60
+ words << (feminine ? ONES_FEM[ones] : ONES_MASC[ones]) if ones.positive?
61
+ end
62
+
63
+ words << pluralize(number, *SCALES[scale_idx]) unless scale_idx.zero?
64
+ words.compact
65
+ end
66
+
67
+ def date_day(day, format:, date_case:)
68
+ return ordinal(day, :default, gender: :masculine) if date_case.to_sym == :genitive
69
+
70
+ ordinal(day, :nominative, gender: :neuter)
71
+ end
72
+
73
+ def date_year(year, format:)
74
+ ordinal(year, :default)
75
+ end
76
+
77
+ def currency_major_feminine?(currency)
78
+ FEMININE_MAJOR_CURRENCIES.include?(currency.to_sym)
79
+ end
80
+
81
+ def currency_minor_feminine?(currency)
82
+ FEMININE_MINOR_CURRENCIES.include?(currency.to_sym)
83
+ end
84
+
85
+ def currency_number_words(number, currency, unit:)
86
+ cardinal(number, feminine: unit == :major ? currency_major_feminine?(currency) : currency_minor_feminine?(currency))
87
+ end
88
+
89
+ def time_unit_feminine?(unit)
90
+ %i[hour minute second].include?(unit)
91
+ end
92
+
93
+ def time_number_words(number, unit:)
94
+ cardinal(number, feminine: time_unit_feminine?(unit))
95
+ end
96
+
97
+ def ordinal(value, format, gender: :masculine)
98
+ ordinals = ORDINALS[format]
99
+ gender_data = ordinals[gender] || ordinals[:masculine]
100
+
101
+ return gender_data[value - 1] if value.between?(1, gender_data.length)
102
+
103
+ if value > 31
104
+ thousands = (value / 100) * 100
105
+ last_two = value % 100
106
+ base_year = cardinal(thousands)
107
+ last_ordinal = gender_data[last_two - 1] || cardinal(last_two)
108
+
109
+ return [base_year, last_ordinal].join(" ")
110
+ end
111
+
112
+ cardinal(value)
113
+ end
114
+
115
+ def cardinal(number, feminine: false)
116
+ integer_value = Integer(number)
117
+ negative = integer_value.negative?
118
+ integer_value = integer_value.abs
119
+
120
+ return (feminine ? ONES_FEM[0] : ONES_MASC[0]) if integer_value.zero?
121
+
122
+ groups = integer_value.to_s
123
+ .chars.reverse.each_slice(3).map(&:reverse)
124
+ .map(&:join).map!(&:to_i).reverse
125
+
126
+ words = []
127
+ groups.each_with_index do |group_value, index|
128
+ scale_idx = groups.size - index - 1
129
+ group_feminine = feminine_group?(scale_idx) || feminine
130
+ words.concat triple_to_words(group_value, scale_idx, feminine: group_feminine)
131
+ end
132
+
133
+ words.unshift(minus_word) if negative
134
+ words.join(" ")
135
+ end
136
+
137
+ def pluralize(number, singular, few, plural)
138
+ number = number.abs
139
+ return plural if (11..14).include?(number % 100)
140
+
141
+ case number % 10
142
+ when 1 then singular
143
+ when 2..4 then few
144
+ else plural
145
+ end
146
+ end
15
147
  end
16
148
 
17
149
  register :uk, UK
@@ -10,8 +10,136 @@ module Num2words
10
10
  HUNDREDS = I18n.t("num2words.hundreds", locale: :ur)
11
11
  SCALES = I18n.t("num2words.scales", locale: :ur)
12
12
 
13
- MAJOR_UNIT = I18n.t("num2words.currencies.PKR.name", locale: :ur)
14
- MINOR_UNIT = I18n.t("num2words.currencies.PKR.fractional.name", locale: :ur)
13
+ FRACTIONS = I18n.t("num2words.fractions", locale: :ur)
14
+ GRAMMAR = I18n.t("num2words.grammar", locale: :ur)
15
+
16
+ DATE = I18n.t("num2words.date", locale: :ur)
17
+ DATE_TEMPLATE = I18n.t("num2words.date.template", locale: :ur)
18
+ TIME = I18n.t("num2words.time", locale: :ur)
19
+ TIME_TEMPLATE = I18n.t("num2words.time.template", locale: :ur)
20
+ DATETIME_TEMPLATE = I18n.t("num2words.datetime.template", locale: :ur)
21
+
22
+ ORDINALS = I18n.t("num2words.numbers.ordinals", locale: :ur)
23
+
24
+ UNDER_HUNDRED = [
25
+ "صفر", "ایک", "دو", "تین", "چار", "پانچ", "چھ", "سات", "آٹھ", "نو",
26
+ "دس", "گیارہ", "بارہ", "تیرہ", "چودہ", "پندرہ", "سولہ", "سترہ", "اٹھارہ", "انیس",
27
+ "بیس", "اکیس", "بائیس", "تئیس", "چوبیس", "پچیس", "چھبیس", "ستائیس", "اٹھائیس", "انتیس",
28
+ "تیس", "اکتیس", "بتیس", "تینتیس", "چونتیس", "پینتیس", "چھتیس", "سینتیس", "اڑتیس", "انتالیس",
29
+ "چالیس", "اکتالیس", "بیالیس", "تینتالیس", "چوالیس", "پینتالیس", "چھیالیس", "سینتالیس", "اڑتالیس", "انچاس",
30
+ "پچاس", "اکیاون", "باون", "ترپن", "چون", "پچپن", "چھپن", "ستاون", "اٹھاون", "انسٹھ",
31
+ "ساٹھ", "اکسٹھ", "باسٹھ", "تریسٹھ", "چونسٹھ", "پینسٹھ", "چھیاسٹھ", "سڑسٹھ", "اڑسٹھ", "انہتر",
32
+ "ستر", "اکہتر", "بہتر", "تہتر", "چوہتر", "پچھتر", "چھہتر", "ستتر", "اٹھہتر", "اناسی",
33
+ "اسی", "اکیاسی", "بیاسی", "تراسی", "چوراسی", "پچاسی", "چھیاسی", "ستاسی", "اٹھاسی", "نواسی",
34
+ "نوے", "اکیانوے", "بانوے", "ترانوے", "چورانوے", "پچانوے", "چھیانوے", "ستانوے", "اٹھانوے", "ننانوے"
35
+ ].freeze
36
+
37
+ INDIAN_SCALES = [
38
+ [1_000_000_000, "ارب"],
39
+ [10_000_000, "کروڑ"],
40
+ [100_000, "لاکھ"],
41
+ [1_000, "ہزار"]
42
+ ].freeze
43
+
44
+ module_function
45
+
46
+ def integer_to_words(number, feminine: false)
47
+ cardinal(number)
48
+ end
49
+
50
+ def triple_to_words(number, scale_idx, feminine: false)
51
+ return [] if number.zero?
52
+
53
+ words = [under_thousand(number)]
54
+ words << SCALES[scale_idx][0] unless scale_idx.zero?
55
+ words
56
+ end
57
+
58
+ def minus_word
59
+ GRAMMAR[:minus]
60
+ end
61
+
62
+ def fraction_joiner(joiner)
63
+ joiner.to_sym == :and ? "اور" : GRAMMAR[:conjunction]
64
+ end
65
+
66
+ def default_fraction_word
67
+ GRAMMAR[:default_fraction]
68
+ end
69
+
70
+ def fraction_numerator_feminine?
71
+ false
72
+ end
73
+
74
+ def decimal_separator_word
75
+ "اعشاریہ"
76
+ end
77
+
78
+ def decimal_fraction_words(fraction_string)
79
+ fraction_string.chars.map { |digit| cardinal(digit.to_i) }.join(" ")
80
+ end
81
+
82
+ def date_day(day, format:, date_case:)
83
+ ordinal(day, :default)
84
+ end
85
+
86
+ def date_year(year, format:)
87
+ cardinal(year)
88
+ end
89
+
90
+ def currency_number_words(number, currency, unit:)
91
+ cardinal(number)
92
+ end
93
+
94
+ def time_number_words(number, unit:)
95
+ cardinal(number)
96
+ end
97
+
98
+ def ordinal(value, format, gender: :masculine)
99
+ ordinals = ORDINALS[format] || ORDINALS[:default] || ORDINALS[:nominative]
100
+ gender_data = ordinals[gender] || ordinals[:masculine]
101
+
102
+ return gender_data[value - 1] if value.between?(1, gender_data.length)
103
+
104
+ cardinal(value)
105
+ end
106
+
107
+ def cardinal(number)
108
+ integer_value = Integer(number)
109
+ negative = integer_value.negative?
110
+ integer_value = integer_value.abs
111
+
112
+ return UNDER_HUNDRED[0] if integer_value.zero?
113
+
114
+ remainder = integer_value
115
+ words = []
116
+
117
+ INDIAN_SCALES.each do |value, label|
118
+ next if remainder < value
119
+
120
+ group_value = remainder / value
121
+ remainder %= value
122
+ words << [cardinal(group_value), label].join(" ")
123
+ end
124
+
125
+ words << under_thousand(remainder) if remainder.positive?
126
+ words.unshift(minus_word) if negative
127
+ words.join(" ")
128
+ end
129
+
130
+ def under_thousand(number)
131
+ return UNDER_HUNDRED[number] if number < 100
132
+
133
+ hundreds = number / 100
134
+ rest = number % 100
135
+ words = [HUNDREDS[hundreds]]
136
+ words << UNDER_HUNDRED[rest] if rest.positive?
137
+ words.join(" ")
138
+ end
139
+
140
+ def pluralize(number, singular, few, _plural)
141
+ number.abs == 1 ? singular : few
142
+ end
15
143
  end
16
144
 
17
145
  register :ur, UR
@@ -10,8 +10,148 @@ module Num2words
10
10
  HUNDREDS = I18n.t("num2words.hundreds", locale: :vi)
11
11
  SCALES = I18n.t("num2words.scales", locale: :vi)
12
12
 
13
- MAJOR_UNIT = I18n.t("num2words.currencies.VND.name", locale: :vi)
14
- MINOR_UNIT = I18n.t("num2words.currencies.VND.fractional.name", locale: :vi)
13
+ FRACTIONS = I18n.t("num2words.fractions", locale: :vi)
14
+ GRAMMAR = I18n.t("num2words.grammar", locale: :vi)
15
+
16
+ DATE = I18n.t("num2words.date", locale: :vi)
17
+ DATE_TEMPLATE = I18n.t("num2words.date.template", locale: :vi)
18
+ TIME = I18n.t("num2words.time", locale: :vi)
19
+ TIME_TEMPLATE = I18n.t("num2words.time.template", locale: :vi)
20
+ DATETIME_TEMPLATE = I18n.t("num2words.datetime.template", locale: :vi)
21
+
22
+ ORDINALS = I18n.t("num2words.numbers.ordinals", locale: :vi)
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 = [under_thousand(number)]
34
+ words << SCALES[scale_idx][0] unless scale_idx.zero?
35
+ words
36
+ end
37
+
38
+ def minus_word
39
+ GRAMMAR[:minus]
40
+ end
41
+
42
+ def fraction_joiner(joiner)
43
+ joiner.to_sym == :and ? "và" : GRAMMAR[:conjunction]
44
+ end
45
+
46
+ def default_fraction_word
47
+ GRAMMAR[:default_fraction]
48
+ end
49
+
50
+ def fraction_numerator_feminine?
51
+ false
52
+ end
53
+
54
+ def decimal_separator_word
55
+ "phẩy"
56
+ end
57
+
58
+ def decimal_fraction_words(fraction_string)
59
+ fraction_string.chars.map { |digit| cardinal(digit.to_i) }.join(" ")
60
+ end
61
+
62
+ def date_day(day, format:, date_case:)
63
+ cardinal(day)
64
+ end
65
+
66
+ def date_year(year, format:)
67
+ cardinal(year)
68
+ end
69
+
70
+ def currency_number_words(number, currency, unit:)
71
+ cardinal(number)
72
+ end
73
+
74
+ def time_number_words(number, unit:)
75
+ cardinal(number)
76
+ end
77
+
78
+ def ordinal(value, format, gender: :masculine)
79
+ ordinals = ORDINALS[format] || ORDINALS[:default] || 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)
88
+ integer_value = Integer(number)
89
+ negative = integer_value.negative?
90
+ integer_value = integer_value.abs
91
+
92
+ return ONES_MASC[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
+ words = []
98
+
99
+ groups.each_with_index do |group_value, index|
100
+ next if group_value.zero?
101
+
102
+ scale_idx = groups.size - index - 1
103
+ full_group = words.any? && group_value < 100
104
+ group_words = under_thousand(group_value, full: full_group)
105
+ words << [group_words, SCALES[scale_idx][0]].reject(&:empty?).join(" ")
106
+ end
107
+
108
+ words.unshift(minus_word) if negative
109
+ words.join(" ")
110
+ end
111
+
112
+ def under_thousand(number, full: false)
113
+ return ONES_MASC[number] if number < 10 && !full
114
+ return under_hundred(number, after_hundreds: false) if number < 100 && !full
115
+
116
+ hundreds = number / 100
117
+ rest = number % 100
118
+ words = []
119
+
120
+ words << (hundreds.positive? ? HUNDREDS[hundreds] : "không trăm")
121
+ words << under_hundred(rest, after_hundreds: true) if rest.positive?
122
+ words.join(" ")
123
+ end
124
+
125
+ def under_hundred(number, after_hundreds:)
126
+ return ["linh", ONES_MASC[number]].join(" ") if number < 10 && after_hundreds
127
+ return ONES_MASC[number] if number < 10
128
+ return "mười" if number == 10
129
+
130
+ if number < 20
131
+ ones = number % 10
132
+ return "mười lăm" if ones == 5
133
+
134
+ return ["mười", ONES_MASC[ones]].join(" ")
135
+ end
136
+
137
+ tens = number / 10
138
+ ones = number % 10
139
+ words = [TENS[tens]]
140
+
141
+ words << case ones
142
+ when 0 then nil
143
+ when 1 then "mốt"
144
+ when 4 then "tư"
145
+ when 5 then "lăm"
146
+ else ONES_MASC[ones]
147
+ end
148
+
149
+ words.compact.join(" ")
150
+ end
151
+
152
+ def pluralize(number, singular, _few, _plural)
153
+ singular
154
+ end
15
155
  end
16
156
 
17
157
  register :vi, VI
@@ -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.1.6"
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")
@@ -25,5 +25,5 @@ Gem::Specification.new do |spec|
25
25
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
26
26
  spec.require_paths = ["lib"]
27
27
 
28
- spec.add_runtime_dependency "i18n", ">= 1.8"
28
+ spec.add_runtime_dependency "i18n", "~> 1.8"
29
29
  end