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
@@ -20,6 +20,118 @@ module Num2words
20
20
  DATETIME_TEMPLATE = I18n.t("num2words.datetime.template", locale: :bn)
21
21
 
22
22
  ORDINALS = I18n.t("num2words.numbers.ordinals", locale: :bn)
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
+ GRAMMAR[:conjunction]
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, :nominative, gender: :neuter)
84
+ end
85
+
86
+ def date_year(year, format:)
87
+ cardinal(year)
88
+ end
89
+
90
+ def ordinal(value, format, gender: :masculine)
91
+ ordinals = ORDINALS[format] || ORDINALS[:nominative]
92
+ gender_data = ordinals[gender] || ordinals[:masculine]
93
+
94
+ return gender_data[value - 1] if value.between?(1, gender_data.length)
95
+
96
+ cardinal(value)
97
+ end
98
+
99
+ def cardinal(number)
100
+ integer_value = Integer(number)
101
+ negative = integer_value.negative?
102
+ integer_value = integer_value.abs
103
+
104
+ return UNDER_HUNDRED[0] if integer_value.zero?
105
+
106
+ remainder = integer_value
107
+ words = []
108
+
109
+ INDIAN_SCALES.each do |value, label|
110
+ next if remainder < value
111
+
112
+ group_value = remainder / value
113
+ remainder %= value
114
+ words << [cardinal(group_value), label].join(" ")
115
+ end
116
+
117
+ words << under_thousand(remainder) if remainder.positive?
118
+ words.unshift(minus_word) if negative
119
+ words.join(" ")
120
+ end
121
+
122
+ def under_thousand(number)
123
+ return UNDER_HUNDRED[number] if number < 100
124
+
125
+ hundreds = number / 100
126
+ rest = number % 100
127
+ words = ["#{UNDER_HUNDRED[hundreds]}শ"]
128
+ words << UNDER_HUNDRED[rest] if rest.positive?
129
+ words.join(" ")
130
+ end
131
+
132
+ def pluralize(_number, singular, _few, _plural)
133
+ singular
134
+ end
23
135
  end
24
136
 
25
137
  register :bn, BN
@@ -20,6 +20,112 @@ module Num2words
20
20
  DATETIME_TEMPLATE = I18n.t("num2words.datetime.template", locale: :cs)
21
21
 
22
22
  ORDINALS = I18n.t("num2words.numbers.ordinals", locale: :cs)
23
+
24
+ module_function
25
+
26
+ def fraction_numerator_feminine?
27
+ true
28
+ end
29
+
30
+ def feminine_group?(scale_idx)
31
+ scale_idx == 3
32
+ end
33
+
34
+ def currency_major_feminine?(currency)
35
+ %i[CZK GBP UAH].include?(currency)
36
+ end
37
+
38
+ def currency_minor_feminine?(currency)
39
+ false
40
+ end
41
+
42
+ def minus_word
43
+ GRAMMAR[:minus]
44
+ end
45
+
46
+ def fraction_joiner(joiner)
47
+ joiner.to_sym == :and ? "a" : GRAMMAR[:conjunction]
48
+ end
49
+
50
+ def default_fraction_word
51
+ GRAMMAR[:default_fraction]
52
+ end
53
+
54
+ def integer_to_words(number, feminine: false)
55
+ cardinal(number, feminine: feminine)
56
+ end
57
+
58
+ def triple_to_words(number, scale_idx, feminine: false)
59
+ return [] if number.zero?
60
+
61
+ words = []
62
+ words << HUNDREDS[number / 100] if number >= 100
63
+ rest = number % 100
64
+
65
+ if rest.between?(10, 19)
66
+ words << TEENS[rest - 10]
67
+ else
68
+ words << TENS[rest / 10] if rest >= 20
69
+ ones = rest % 10
70
+ words << (feminine ? ONES_FEM[ones] : ONES_MASC[ones]) if ones.positive?
71
+ end
72
+
73
+ words << pluralize(number, *SCALES[scale_idx]) unless scale_idx.zero?
74
+ words.compact
75
+ end
76
+
77
+ def date_day(day, format:, date_case:)
78
+ ordinal(day, :default)
79
+ end
80
+
81
+ def date_year(year, format:)
82
+ cardinal(year)
83
+ end
84
+
85
+ def ordinal(value, format, gender: :masculine)
86
+ ordinals = ORDINALS[format]
87
+ gender_data = ordinals[gender] || ordinals[:masculine]
88
+
89
+ return gender_data[value - 1] if value.between?(1, gender_data.length)
90
+
91
+ cardinal(value)
92
+ end
93
+
94
+ def cardinal(number, feminine: false)
95
+ integer_value = Integer(number)
96
+ negative = integer_value.negative?
97
+ integer_value = integer_value.abs
98
+
99
+ return (feminine ? ONES_FEM[0] : ONES_MASC[0]) if integer_value.zero?
100
+
101
+ groups = integer_value.to_s
102
+ .chars.reverse.each_slice(3).map(&:reverse)
103
+ .map(&:join).map!(&:to_i).reverse
104
+
105
+ words = []
106
+ groups.each_with_index do |group_value, index|
107
+ next if group_value.zero?
108
+
109
+ scale_idx = groups.size - index - 1
110
+ group_words = triple_to_words(group_value, scale_idx, feminine: feminine_group?(scale_idx) || (feminine && scale_idx.zero?))
111
+ group_words.shift if scale_idx == 1 && group_value == 1
112
+ words.concat group_words
113
+ end
114
+
115
+ words.unshift(minus_word) if negative
116
+ words.join(" ")
117
+ end
118
+
119
+ def pluralize(number, singular, few, plural)
120
+ number = number.abs
121
+ return plural if (11..14).include?(number % 100)
122
+
123
+ case number % 10
124
+ when 1 then singular
125
+ when 2..4 then few
126
+ else plural
127
+ end
128
+ end
23
129
  end
24
130
 
25
131
  register :cs, CS
@@ -20,6 +20,103 @@ module Num2words
20
20
  DATETIME_TEMPLATE = I18n.t("num2words.datetime.template", locale: :da)
21
21
 
22
22
  ORDINALS = I18n.t("num2words.numbers.ordinals", locale: :da)
23
+
24
+ module_function
25
+
26
+ def minus_word
27
+ GRAMMAR[:minus]
28
+ end
29
+
30
+ def fraction_joiner(joiner)
31
+ joiner.to_sym == :and ? "og" : GRAMMAR[:conjunction]
32
+ end
33
+
34
+ def default_fraction_word
35
+ GRAMMAR[:default_fraction]
36
+ end
37
+
38
+ def fraction_numerator_feminine?
39
+ false
40
+ end
41
+
42
+ def integer_to_words(number, feminine: false)
43
+ cardinal(number, feminine: feminine)
44
+ end
45
+
46
+ def date_day(day, format:, date_case:)
47
+ ordinal(day, :nominative)
48
+ end
49
+
50
+ def triple_to_words(number, scale_idx, feminine: false)
51
+ triple_words(number, feminine: feminine).tap do |words|
52
+ words << pluralize(number, *SCALES[scale_idx]) unless scale_idx.zero?
53
+ end
54
+ end
55
+
56
+ def date_year(year, format:)
57
+ cardinal(year)
58
+ end
59
+
60
+ def ordinal(value, format, gender: :masculine)
61
+ ordinals = ORDINALS[format] || ORDINALS[:nominative]
62
+ gender_data = ordinals[gender] || ordinals[:masculine]
63
+
64
+ return gender_data[value - 1] if value.between?(1, gender_data.length)
65
+
66
+ cardinal(value)
67
+ end
68
+
69
+ def pluralize(number, singular, few, plural)
70
+ number.abs == 1 ? singular : plural
71
+ end
72
+
73
+ def cardinal(number, feminine: false)
74
+ integer_value = Integer(number)
75
+ negative = integer_value.negative?
76
+ integer_value = integer_value.abs
77
+
78
+ return (feminine ? ONES_FEM[0] : ONES_MASC[0]) if integer_value.zero?
79
+
80
+ groups = integer_value.to_s
81
+ .chars.reverse.each_slice(3).map(&:reverse)
82
+ .map(&:join).map!(&:to_i).reverse
83
+
84
+ words = []
85
+ groups.each_with_index do |group_value, index|
86
+ next if group_value.zero?
87
+
88
+ scale_idx = groups.size - index - 1
89
+ group_words = triple_to_words(group_value, scale_idx, feminine: feminine && scale_idx.zero?)
90
+ group_words[0] = "et" if scale_idx == 1 && group_value == 1
91
+ group_words.unshift("og") if scale_idx.zero? && words.any? && group_value < 100
92
+ words.concat(group_words)
93
+ end
94
+
95
+ words.unshift(minus_word) if negative
96
+ words.join(" ")
97
+ end
98
+
99
+ def triple_words(number, feminine: false)
100
+ return [] if number.zero?
101
+
102
+ words = []
103
+ hundreds = number / 100
104
+ rest = number % 100
105
+
106
+ words << HUNDREDS[hundreds] if hundreds.positive?
107
+ words << "og" if hundreds.positive? && rest.positive?
108
+ words << under_hundred(rest, feminine: feminine) if rest.positive?
109
+ words.compact
110
+ end
111
+
112
+ def under_hundred(number, feminine: false)
113
+ return TEENS[number - 10] if number.between?(10, 19)
114
+ return TENS[number / 10] if (number % 10).zero?
115
+ return (feminine ? ONES_FEM[number] : ONES_MASC[number]) if number < 10
116
+
117
+ ones = feminine ? ONES_FEM[number % 10] : ONES_MASC[number % 10]
118
+ "#{ones}og#{TENS[number / 10]}"
119
+ end
23
120
  end
24
121
 
25
122
  register :da, DA
@@ -20,6 +20,125 @@ module Num2words
20
20
  DATETIME_TEMPLATE = I18n.t("num2words.datetime.template", locale: :de)
21
21
 
22
22
  ORDINALS = I18n.t("num2words.numbers.ordinals", locale: :de)
23
+
24
+ module_function
25
+
26
+ def feminine_group?(scale_idx)
27
+ scale_idx >= 2
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
+ joiner.to_sym == :and ? "und" : GRAMMAR[:conjunction]
40
+ end
41
+
42
+ def default_fraction_word
43
+ GRAMMAR[:default_fraction]
44
+ end
45
+
46
+ def integer_to_words(number, feminine: false)
47
+ cardinal(number, feminine: feminine)
48
+ end
49
+
50
+ def currency_major_feminine?(currency)
51
+ %i[UAH CZK SEK NOK DKK INR IDR PKR].include?(currency)
52
+ end
53
+
54
+ def currency_minor_feminine?(currency)
55
+ false
56
+ end
57
+
58
+ def currency_number_words(number, currency, unit:)
59
+ return cardinal(number, feminine: currency_major_feminine?(currency)) unless Integer(number) == 1
60
+
61
+ unit == :major && currency_major_feminine?(currency) ? "eine" : "ein"
62
+ end
63
+
64
+ def triple_to_words(number, scale_idx, feminine: false)
65
+ return [] if number.zero?
66
+
67
+ words = [under_thousand(number, feminine: feminine)]
68
+ words << pluralize(number, *SCALES[scale_idx]) unless scale_idx.zero?
69
+ words
70
+ end
71
+
72
+ def date_day(day, format:, date_case:)
73
+ ordinal(day, :default)
74
+ end
75
+
76
+ def date_year(year, format:)
77
+ cardinal(year).delete(" ")
78
+ end
79
+
80
+ def ordinal(value, format, gender: :masculine)
81
+ ordinals = ORDINALS[format] || ORDINALS[:nominative]
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
+ next if group_value.zero?
103
+
104
+ scale_idx = groups.size - index - 1
105
+ group_feminine = feminine_group?(scale_idx) || (feminine && scale_idx.zero?)
106
+ group_words = triple_to_words(group_value, scale_idx, feminine: group_feminine)
107
+ group_words.shift if scale_idx == 1 && group_value == 1
108
+ words.concat(group_words)
109
+ end
110
+
111
+ words.unshift(minus_word) if negative
112
+ words.join(" ")
113
+ end
114
+
115
+ def under_thousand(number, feminine: false)
116
+ return under_hundred(number, feminine: feminine) if number < 100
117
+
118
+ hundreds = number / 100
119
+ rest = number % 100
120
+ hundred_words = hundreds == 1 ? "einhundert" : "#{under_hundred(hundreds)}hundert"
121
+
122
+ return hundred_words if rest.zero?
123
+
124
+ "#{hundred_words}#{under_hundred(rest, feminine: feminine)}"
125
+ end
126
+
127
+ def under_hundred(number, feminine: false)
128
+ return feminine ? ONES_FEM[number] : ONES_MASC[number] if number < 10
129
+ return TEENS[number - 10] if number < 20
130
+ return TENS[number / 10] if (number % 10).zero?
131
+
132
+ ones = number % 10
133
+ tens = number / 10
134
+ ones_word = ones == 1 ? "ein" : ONES_MASC[ones]
135
+
136
+ "#{ones_word}und#{TENS[tens]}"
137
+ end
138
+
139
+ def pluralize(number, singular, few, plural)
140
+ number.abs == 1 ? singular : plural
141
+ end
23
142
  end
24
143
 
25
144
  register :de, DE
@@ -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