num2words 0.2.0 → 0.3.1

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 (117) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ci.yml +40 -0
  3. data/CHANGELOG.md +40 -0
  4. data/README.md +139 -144
  5. data/Rakefile +38 -2
  6. data/benchmark/converter_benchmark.rb +96 -0
  7. data/config/locales/ar.yml +5 -1
  8. data/config/locales/be.yml +4 -0
  9. data/config/locales/bg.yml +4 -0
  10. data/config/locales/bn.yml +4 -0
  11. data/config/locales/cs.yml +6 -2
  12. data/config/locales/da.yml +4 -0
  13. data/config/locales/de.yml +8 -4
  14. data/config/locales/el.yml +4 -0
  15. data/config/locales/en.yml +4 -0
  16. data/config/locales/es.yml +4 -0
  17. data/config/locales/et.yml +9 -5
  18. data/config/locales/fa.yml +15 -11
  19. data/config/locales/fi.yml +8 -4
  20. data/config/locales/fr.yml +4 -0
  21. data/config/locales/gu.yml +4 -0
  22. data/config/locales/he.yml +6 -2
  23. data/config/locales/hi.yml +4 -0
  24. data/config/locales/hr.yml +8 -4
  25. data/config/locales/hu.yml +6 -2
  26. data/config/locales/id.yml +4 -0
  27. data/config/locales/it.yml +4 -0
  28. data/config/locales/ja.yml +10 -5
  29. data/config/locales/kn.yml +186 -10
  30. data/config/locales/ko.yml +184 -13
  31. data/config/locales/kz.yml +183 -17
  32. data/config/locales/lt.yml +184 -8
  33. data/config/locales/lv.yml +188 -12
  34. data/config/locales/ml.yml +189 -13
  35. data/config/locales/mr.yml +184 -8
  36. data/config/locales/ms.yml +184 -8
  37. data/config/locales/nb.yml +183 -17
  38. data/config/locales/nl.yml +184 -13
  39. data/config/locales/pa.yml +189 -13
  40. data/config/locales/pl.yml +184 -13
  41. data/config/locales/pt.yml +184 -13
  42. data/config/locales/ro.yml +183 -12
  43. data/config/locales/ru.yml +4 -0
  44. data/config/locales/sk.yml +184 -8
  45. data/config/locales/sl.yml +185 -9
  46. data/config/locales/sr.yml +186 -15
  47. data/config/locales/sv.yml +182 -13
  48. data/config/locales/sw.yml +184 -8
  49. data/config/locales/ta.yml +184 -8
  50. data/config/locales/te.yml +185 -9
  51. data/config/locales/th.yml +183 -12
  52. data/config/locales/tr.yml +183 -12
  53. data/config/locales/uk.yml +189 -18
  54. data/config/locales/ur.yml +187 -11
  55. data/config/locales/vi.yml +183 -12
  56. data/config/locales/zh.yml +191 -9
  57. data/docs/api.md +422 -0
  58. data/docs/api.ru.md +422 -0
  59. data/docs/limits.md +140 -0
  60. data/docs/limits.ru.md +140 -0
  61. data/docs/locale_development.md +241 -0
  62. data/docs/locale_development.ru.md +241 -0
  63. data/lib/num2words/converter.rb +144 -35
  64. data/lib/num2words/locales/ar.rb +130 -0
  65. data/lib/num2words/locales/be.rb +101 -0
  66. data/lib/num2words/locales/bg.rb +127 -0
  67. data/lib/num2words/locales/bn.rb +112 -0
  68. data/lib/num2words/locales/cs.rb +106 -0
  69. data/lib/num2words/locales/da.rb +97 -0
  70. data/lib/num2words/locales/de.rb +119 -0
  71. data/lib/num2words/locales/el.rb +143 -0
  72. data/lib/num2words/locales/en.rb +90 -0
  73. data/lib/num2words/locales/es.rb +185 -0
  74. data/lib/num2words/locales/et.rb +104 -0
  75. data/lib/num2words/locales/fa.rb +104 -0
  76. data/lib/num2words/locales/fi.rb +104 -0
  77. data/lib/num2words/locales/fr.rb +121 -0
  78. data/lib/num2words/locales/gu.rb +112 -0
  79. data/lib/num2words/locales/he.rb +130 -0
  80. data/lib/num2words/locales/hi.rb +112 -0
  81. data/lib/num2words/locales/hr.rb +117 -0
  82. data/lib/num2words/locales/hu.rb +111 -0
  83. data/lib/num2words/locales/id.rb +104 -0
  84. data/lib/num2words/locales/it.rb +142 -0
  85. data/lib/num2words/locales/ja.rb +126 -0
  86. data/lib/num2words/locales/kn.rb +122 -2
  87. data/lib/num2words/locales/ko.rb +158 -2
  88. data/lib/num2words/locales/kz.rb +137 -2
  89. data/lib/num2words/locales/lt.rb +146 -2
  90. data/lib/num2words/locales/lv.rb +148 -2
  91. data/lib/num2words/locales/ml.rb +142 -2
  92. data/lib/num2words/locales/mr.rb +142 -2
  93. data/lib/num2words/locales/ms.rb +134 -2
  94. data/lib/num2words/locales/nb.rb +153 -0
  95. data/lib/num2words/locales/nl.rb +142 -2
  96. data/lib/num2words/locales/pa.rb +122 -2
  97. data/lib/num2words/locales/pl.rb +149 -2
  98. data/lib/num2words/locales/pt.rb +164 -2
  99. data/lib/num2words/locales/ro.rb +165 -2
  100. data/lib/num2words/locales/ru.rb +100 -0
  101. data/lib/num2words/locales/sk.rb +124 -2
  102. data/lib/num2words/locales/sl.rb +154 -2
  103. data/lib/num2words/locales/sr.rb +141 -3
  104. data/lib/num2words/locales/sv.rb +141 -2
  105. data/lib/num2words/locales/sw.rb +133 -2
  106. data/lib/num2words/locales/ta.rb +142 -2
  107. data/lib/num2words/locales/te.rb +142 -2
  108. data/lib/num2words/locales/th.rb +133 -2
  109. data/lib/num2words/locales/tr.rb +141 -2
  110. data/lib/num2words/locales/uk.rb +134 -2
  111. data/lib/num2words/locales/ur.rb +130 -2
  112. data/lib/num2words/locales/vi.rb +142 -2
  113. data/lib/num2words/locales/zh.rb +172 -2
  114. data/lib/num2words/version.rb +1 -1
  115. data/num2words.gemspec +3 -2
  116. metadata +28 -6
  117. data/lib/num2words/locales/no.rb +0 -19
@@ -0,0 +1,241 @@
1
+ # Locale Development
2
+
3
+ This guide describes how to add or update a locale in `num2words`.
4
+
5
+ Each locale has two main files:
6
+
7
+ - `config/locales/xx.yml` - locale data for numbers, grammar, dates, time and currencies.
8
+ - `lib/num2words/locales/xx.rb` - language-specific rules used by `Num2words::Converter`.
9
+
10
+ Use an existing completed locale as a reference. For simple European plural rules, `ru`, `uk`, `be`, `pl`, `cs` are useful examples. For languages with 4-digit or regional grouping, check `ja`, `ko`, `zh`, `hi`, `pa`, `ur`.
11
+
12
+ ## YAML Structure
13
+
14
+ The YAML root must match the locale code:
15
+
16
+ ```yaml
17
+ xx:
18
+ num2words:
19
+ ones_masc: []
20
+ ones_fem: []
21
+ teens: []
22
+ tens: []
23
+ hundreds: []
24
+ scales: []
25
+
26
+ grammar:
27
+ minus: ""
28
+ conjunction: ""
29
+ default_fraction: ""
30
+
31
+ fractions:
32
+ 10: []
33
+ 100: []
34
+ 1000: []
35
+ 10000: []
36
+ 100000: []
37
+ 1000000: []
38
+ 10000000: []
39
+ 100000000: []
40
+ 1000000000: []
41
+
42
+ numbers:
43
+ ordinals:
44
+ nominative:
45
+ masculine: []
46
+ feminine: []
47
+ neuter: []
48
+ default:
49
+ masculine: []
50
+
51
+ datetime:
52
+ template: "%{date}, %{time}"
53
+
54
+ date:
55
+ template:
56
+ default: "%{day} %{month} %{year}"
57
+ nominative: "%{day} %{month} %{year}"
58
+ months:
59
+ default: []
60
+ nominative: []
61
+
62
+ time:
63
+ template:
64
+ hours_only: "%{hours}"
65
+ hours_minutes: "%{hours} %{minutes}"
66
+ hours_minutes_seconds: "%{hours} %{minutes} %{seconds}"
67
+ words:
68
+ hour: []
69
+ minute: []
70
+ second: []
71
+
72
+ warnings:
73
+ currency_not_available: "Currency %{currency} is not available for locale %{locale}."
74
+
75
+ currencies:
76
+ USD:
77
+ major_unit: []
78
+ minor_unit: []
79
+ symbol: "$"
80
+ ```
81
+
82
+ ## Currency Catalog
83
+
84
+ Every completed locale must contain the same 32 currency codes:
85
+
86
+ ```text
87
+ BDT BGN BRL BYN CNY CZK DKK EUR GBP HUF IDR ILS INR IRR JPY KES KRW
88
+ KZT MYR NOK PKR PLN RON RSD RUB SAR SEK THB TRY UAH USD VND
89
+ ```
90
+
91
+ Each currency entry must include:
92
+
93
+ - `major_unit` - array of unit forms.
94
+ - `minor_unit` - array of minor unit forms.
95
+ - `symbol` - display symbol.
96
+
97
+ Example:
98
+
99
+ ```yaml
100
+ USD:
101
+ major_unit: ["dollar", "dollars", "dollars"]
102
+ minor_unit: ["cent", "cents", "cents"]
103
+ symbol: "$"
104
+ ```
105
+
106
+ If the language does not use plural forms, repeat the same value three times.
107
+
108
+ ## Locale Module
109
+
110
+ Each Ruby locale module must register itself:
111
+
112
+ ```ruby
113
+ # frozen_string_literal: true
114
+
115
+ module Num2words
116
+ module Locales
117
+ module XX
118
+ ONES_MASC = I18n.t("num2words.ones_masc", locale: :xx)
119
+ ONES_FEM = I18n.t("num2words.ones_fem", locale: :xx)
120
+ TEENS = I18n.t("num2words.teens", locale: :xx)
121
+ TENS = I18n.t("num2words.tens", locale: :xx)
122
+ HUNDREDS = I18n.t("num2words.hundreds", locale: :xx)
123
+ SCALES = I18n.t("num2words.scales", locale: :xx)
124
+
125
+ FRACTIONS = I18n.t("num2words.fractions", locale: :xx)
126
+ GRAMMAR = I18n.t("num2words.grammar", locale: :xx)
127
+
128
+ DATE = I18n.t("num2words.date", locale: :xx)
129
+ DATE_TEMPLATE = I18n.t("num2words.date.template", locale: :xx)
130
+ TIME = I18n.t("num2words.time", locale: :xx)
131
+ TIME_TEMPLATE = I18n.t("num2words.time.template", locale: :xx)
132
+ DATETIME_TEMPLATE = I18n.t("num2words.datetime.template", locale: :xx)
133
+
134
+ ORDINALS = I18n.t("num2words.numbers.ordinals", locale: :xx)
135
+
136
+ module_function
137
+ end
138
+
139
+ register :xx, XX
140
+ end
141
+ end
142
+ ```
143
+
144
+ ## Common Hooks
145
+
146
+ `Converter` handles common flow and calls locale hooks when they exist.
147
+
148
+ Number hooks:
149
+
150
+ - `integer_to_words(number, feminine: false)` - fully custom integer conversion.
151
+ - `triple_to_words(number, scale_idx, feminine: false)` - conversion for a 0..999 group.
152
+ - `feminine_group?(scale_idx)` - whether a scale group uses feminine forms.
153
+ - `pluralize(number, singular, few, plural)` - local plural rule.
154
+
155
+ Fraction hooks:
156
+
157
+ - `minus_word`
158
+ - `fraction_joiner(joiner)`
159
+ - `default_fraction_word`
160
+ - `fraction_numerator_feminine?`
161
+ - `decimal_separator_word`
162
+ - `decimal_fraction_words(fraction_string)`
163
+ - `join_decimal_words(words)`
164
+ - `join_fraction_words(words)`
165
+
166
+ Date and time hooks:
167
+
168
+ - `date_day(day, format:, date_case:)`
169
+ - `date_year(year, format:)`
170
+ - `time_unit_feminine?(unit)`
171
+ - `time_number_words(number, unit:)`
172
+ - `join_time_words(number_words, unit_words)`
173
+
174
+ Currency hooks:
175
+
176
+ - `currency_major_feminine?(currency)`
177
+ - `currency_minor_feminine?(currency)`
178
+ - `currency_number_words(number, currency, unit:)`
179
+ - `join_currency_parts(parts)`
180
+
181
+ Use hooks only when the language needs them. If the common converter behavior is enough, keep the locale module smaller.
182
+
183
+ ## Specs
184
+
185
+ Add a dedicated spec:
186
+
187
+ ```text
188
+ spec/xx_to_words_spec.rb
189
+ ```
190
+
191
+ Minimum coverage should include:
192
+
193
+ - zero and basic integers;
194
+ - tens, hundreds and scale values;
195
+ - large compound numbers;
196
+ - negative numbers;
197
+ - string integer input;
198
+ - decimal string input with dot and comma;
199
+ - fraction style and `joiner: :and`;
200
+ - `word_case`;
201
+ - `to_currency` with default currency, another currency and `minor:` options;
202
+ - date, time and datetime.
203
+
204
+ Then add the locale code to:
205
+
206
+ ```text
207
+ spec/currency_catalog_spec.rb
208
+ ```
209
+
210
+ The locale API smoke spec discovers YAML files automatically, so no manual update is needed there.
211
+
212
+ ## Verification
213
+
214
+ Run targeted tests first:
215
+
216
+ ```bash
217
+ bundle exec rspec spec/xx_to_words_spec.rb spec/currency_catalog_spec.rb spec/locale_api_smoke_spec.rb
218
+ ```
219
+
220
+ Then run the full suite:
221
+
222
+ ```bash
223
+ bundle exec rspec
224
+ ```
225
+
226
+ Before release, also build the gem:
227
+
228
+ ```bash
229
+ gem build num2words.gemspec
230
+ ```
231
+
232
+ ## Practical Checklist
233
+
234
+ - YAML root matches the locale code.
235
+ - Locale module registers with `register :xx, XX`.
236
+ - `GRAMMAR`, `FRACTIONS`, `DATE`, `TIME`, `DATETIME_TEMPLATE` and `ORDINALS` constants exist.
237
+ - All 32 currencies exist and have `major_unit`, `minor_unit`, `symbol`.
238
+ - Default currency is the first currency in YAML.
239
+ - Dates and times work through `Num2words.to_words`.
240
+ - Currency works through `Num2words.to_currency`.
241
+ - Full test suite passes.
@@ -0,0 +1,241 @@
1
+ # Разработка локалей
2
+
3
+ Эта инструкция описывает, как добавить новую локаль или актуализировать существующую в `num2words`.
4
+
5
+ Каждая локаль состоит из двух основных файлов:
6
+
7
+ - `config/locales/xx.yml` - данные локали: числа, грамматика, даты, время и валюты.
8
+ - `lib/num2words/locales/xx.rb` - языковые правила, которые использует `Num2words::Converter`.
9
+
10
+ В качестве примера лучше брать уже завершенные локали. Для языков с простыми европейскими правилами множественного числа подойдут `ru`, `uk`, `be`, `pl`, `cs`. Для языков с 4-значной или региональной группировкой смотри `ja`, `ko`, `zh`, `hi`, `pa`, `ur`.
11
+
12
+ ## Структура YAML
13
+
14
+ Корневой ключ YAML должен совпадать с кодом локали:
15
+
16
+ ```yaml
17
+ xx:
18
+ num2words:
19
+ ones_masc: []
20
+ ones_fem: []
21
+ teens: []
22
+ tens: []
23
+ hundreds: []
24
+ scales: []
25
+
26
+ grammar:
27
+ minus: ""
28
+ conjunction: ""
29
+ default_fraction: ""
30
+
31
+ fractions:
32
+ 10: []
33
+ 100: []
34
+ 1000: []
35
+ 10000: []
36
+ 100000: []
37
+ 1000000: []
38
+ 10000000: []
39
+ 100000000: []
40
+ 1000000000: []
41
+
42
+ numbers:
43
+ ordinals:
44
+ nominative:
45
+ masculine: []
46
+ feminine: []
47
+ neuter: []
48
+ default:
49
+ masculine: []
50
+
51
+ datetime:
52
+ template: "%{date}, %{time}"
53
+
54
+ date:
55
+ template:
56
+ default: "%{day} %{month} %{year}"
57
+ nominative: "%{day} %{month} %{year}"
58
+ months:
59
+ default: []
60
+ nominative: []
61
+
62
+ time:
63
+ template:
64
+ hours_only: "%{hours}"
65
+ hours_minutes: "%{hours} %{minutes}"
66
+ hours_minutes_seconds: "%{hours} %{minutes} %{seconds}"
67
+ words:
68
+ hour: []
69
+ minute: []
70
+ second: []
71
+
72
+ warnings:
73
+ currency_not_available: "Currency %{currency} is not available for locale %{locale}."
74
+
75
+ currencies:
76
+ USD:
77
+ major_unit: []
78
+ minor_unit: []
79
+ symbol: "$"
80
+ ```
81
+
82
+ ## Каталог валют
83
+
84
+ Каждая завершенная локаль должна содержать одинаковый набор из 32 валют:
85
+
86
+ ```text
87
+ BDT BGN BRL BYN CNY CZK DKK EUR GBP HUF IDR ILS INR IRR JPY KES KRW
88
+ KZT MYR NOK PKR PLN RON RSD RUB SAR SEK THB TRY UAH USD VND
89
+ ```
90
+
91
+ Каждая валюта должна содержать:
92
+
93
+ - `major_unit` - формы основной денежной единицы.
94
+ - `minor_unit` - формы младшей денежной единицы.
95
+ - `symbol` - символ валюты.
96
+
97
+ Пример:
98
+
99
+ ```yaml
100
+ USD:
101
+ major_unit: ["доллар", "доллара", "долларов"]
102
+ minor_unit: ["цент", "цента", "центов"]
103
+ symbol: "$"
104
+ ```
105
+
106
+ Если в языке нет склонения по числу, повторить одно и то же значение три раза.
107
+
108
+ ## Ruby-модуль локали
109
+
110
+ Каждый Ruby-модуль локали должен зарегистрировать себя:
111
+
112
+ ```ruby
113
+ # frozen_string_literal: true
114
+
115
+ module Num2words
116
+ module Locales
117
+ module XX
118
+ ONES_MASC = I18n.t("num2words.ones_masc", locale: :xx)
119
+ ONES_FEM = I18n.t("num2words.ones_fem", locale: :xx)
120
+ TEENS = I18n.t("num2words.teens", locale: :xx)
121
+ TENS = I18n.t("num2words.tens", locale: :xx)
122
+ HUNDREDS = I18n.t("num2words.hundreds", locale: :xx)
123
+ SCALES = I18n.t("num2words.scales", locale: :xx)
124
+
125
+ FRACTIONS = I18n.t("num2words.fractions", locale: :xx)
126
+ GRAMMAR = I18n.t("num2words.grammar", locale: :xx)
127
+
128
+ DATE = I18n.t("num2words.date", locale: :xx)
129
+ DATE_TEMPLATE = I18n.t("num2words.date.template", locale: :xx)
130
+ TIME = I18n.t("num2words.time", locale: :xx)
131
+ TIME_TEMPLATE = I18n.t("num2words.time.template", locale: :xx)
132
+ DATETIME_TEMPLATE = I18n.t("num2words.datetime.template", locale: :xx)
133
+
134
+ ORDINALS = I18n.t("num2words.numbers.ordinals", locale: :xx)
135
+
136
+ module_function
137
+ end
138
+
139
+ register :xx, XX
140
+ end
141
+ end
142
+ ```
143
+
144
+ ## Основные hooks
145
+
146
+ `Converter` отвечает за общий поток выполнения и вызывает методы локали, если они определены.
147
+
148
+ Hooks для чисел:
149
+
150
+ - `integer_to_words(number, feminine: false)` - полностью кастомное преобразование целых чисел.
151
+ - `triple_to_words(number, scale_idx, feminine: false)` - преобразование группы 0..999.
152
+ - `feminine_group?(scale_idx)` - нужно ли использовать женский род для группы разрядов.
153
+ - `pluralize(number, singular, few, plural)` - локальное правило множественного числа.
154
+
155
+ Hooks для дробей:
156
+
157
+ - `minus_word`
158
+ - `fraction_joiner(joiner)`
159
+ - `default_fraction_word`
160
+ - `fraction_numerator_feminine?`
161
+ - `decimal_separator_word`
162
+ - `decimal_fraction_words(fraction_string)`
163
+ - `join_decimal_words(words)`
164
+ - `join_fraction_words(words)`
165
+
166
+ Hooks для даты и времени:
167
+
168
+ - `date_day(day, format:, date_case:)`
169
+ - `date_year(year, format:)`
170
+ - `time_unit_feminine?(unit)`
171
+ - `time_number_words(number, unit:)`
172
+ - `join_time_words(number_words, unit_words)`
173
+
174
+ Hooks для валют:
175
+
176
+ - `currency_major_feminine?(currency)`
177
+ - `currency_minor_feminine?(currency)`
178
+ - `currency_number_words(number, currency, unit:)`
179
+ - `join_currency_parts(parts)`
180
+
181
+ Добавлять hook только если язык действительно требует отдельного поведения. Если общего поведения converter достаточно, модуль локали лучше оставить проще.
182
+
183
+ ## Тесты
184
+
185
+ Для каждой локали нужен отдельный spec:
186
+
187
+ ```text
188
+ spec/xx_to_words_spec.rb
189
+ ```
190
+
191
+ Минимальное покрытие:
192
+
193
+ - ноль и базовые целые числа;
194
+ - десятки, сотни и разряды;
195
+ - большие составные числа;
196
+ - отрицательные числа;
197
+ - целое число строкой;
198
+ - дробная строка с точкой и запятой;
199
+ - дроби и `joiner: :and`;
200
+ - `word_case`;
201
+ - `to_currency` с валютой по умолчанию, другой валютой и опциями `minor:`;
202
+ - дата, время и дата-время.
203
+
204
+ После добавления локали добавть ее код в:
205
+
206
+ ```text
207
+ spec/currency_catalog_spec.rb
208
+ ```
209
+
210
+ `locale_api_smoke_spec` находит YAML-файлы автоматически, поэтому вручную обновлять его не нужно.
211
+
212
+ ## Проверка
213
+
214
+ Сначала запустить целевые тесты:
215
+
216
+ ```bash
217
+ bundle exec rspec spec/xx_to_words_spec.rb spec/currency_catalog_spec.rb spec/locale_api_smoke_spec.rb
218
+ ```
219
+
220
+ Потом полный набор:
221
+
222
+ ```bash
223
+ bundle exec rspec
224
+ ```
225
+
226
+ Перед релизом также проверить сборку гема:
227
+
228
+ ```bash
229
+ gem build num2words.gemspec
230
+ ```
231
+
232
+ ## Практический чеклист
233
+
234
+ - Корневой ключ YAML совпадает с кодом локали.
235
+ - Ruby-модуль зарегистрирован через `register :xx, XX`.
236
+ - Есть константы `GRAMMAR`, `FRACTIONS`, `DATE`, `TIME`, `DATETIME_TEMPLATE`, `ORDINALS`.
237
+ - Все 32 валюты есть и содержат `major_unit`, `minor_unit`, `symbol`.
238
+ - Валюта по умолчанию стоит первой в YAML.
239
+ - Даты и время работают через `Num2words.to_words`.
240
+ - Валюты работают через `Num2words.to_currency`.
241
+ - Полный набор тестов проходит.