human_number 0.1.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.
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rails/railtie"
4
+
5
+ module HumanNumber
6
+ class Railtie < Rails::Railtie
7
+ initializer "human_number.configure_rails_initialization" do
8
+ ActiveSupport.on_load(:action_view) do
9
+ require "human_number/rails/helpers"
10
+ include HumanNumber::Rails::Helpers
11
+ end
12
+
13
+ ActiveSupport.on_load(:action_controller) do
14
+ require "human_number/rails/helpers"
15
+ include HumanNumber::Rails::Helpers
16
+ end
17
+ end
18
+
19
+ initializer "human_number.load_locale_data" do
20
+ I18n.load_path += Dir[File.expand_path("../../config/locales/*.yml", __dir__)]
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module HumanNumber
4
+ VERSION = "0.1.0"
5
+ end
@@ -0,0 +1,196 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "i18n"
4
+ require "action_view"
5
+ require "rails-i18n"
6
+
7
+ # Ensure Rails I18n locales are available
8
+ I18n.load_path += Dir[File.expand_path("**/*.yml", Gem.loaded_specs["rails-i18n"].full_gem_path)]
9
+
10
+ require_relative "human_number/version"
11
+ require_relative "human_number/locale_support"
12
+ require_relative "human_number/formatters/number"
13
+ require_relative "human_number/formatters/currency"
14
+
15
+ module HumanNumber
16
+ class Error < StandardError; end
17
+
18
+ # Load locale files
19
+ I18n.load_path += Dir[File.expand_path("../config/locales/*.yml", __dir__)]
20
+
21
+ extend ActionView::Helpers::NumberHelper
22
+
23
+ class << self
24
+ # Formats a number with intelligent, locale-aware abbreviations.
25
+ #
26
+ # This method provides culturally-appropriate number formatting with automatic
27
+ # unit system selection based on locale:
28
+ # - **Western locales**: K/M/B/T (thousand/million/billion/trillion)
29
+ # - **East Asian locales**: 천/만/억/조 or 千/万/億/兆
30
+ # - **Indian locales**: thousand/lakh/crore
31
+ #
32
+ # @param number [Numeric] The number to format
33
+ # @param locale [Symbol, String] The locale for formatting (default: current I18n locale)
34
+ # Determines both number formatting rules and cultural unit systems
35
+ #
36
+ # @option options [Boolean] :abbr_units (true) Use abbreviated unit symbols (K/M/B)
37
+ # vs full words. Only affects Western locales
38
+ # @option options [Integer, nil] :max_digits (2) Maximum significant digits to display.
39
+ # When nil, displays complete number using multiple units without decimals
40
+ # @option options [Integer, nil] :min_unit (nil) Minimum unit threshold for abbreviation.
41
+ # Numbers below this won't be abbreviated
42
+ # @option options [Boolean] :trim_zeros (true) Remove trailing decimal zeros.
43
+ # Only applies in abbreviated mode (when max_digits is set)
44
+ #
45
+ # @return [String] The formatted number string
46
+ #
47
+ # @example Basic usage
48
+ # HumanNumber.human_number(1234567) #=> "1.2M"
49
+ # HumanNumber.human_number(50000, locale: :ko) #=> "5만"
50
+ #
51
+ # @example Precision control
52
+ # HumanNumber.human_number(1234567, max_digits: 2) #=> "1.2M"
53
+ # HumanNumber.human_number(1234567, max_digits: nil) #=> "1M 234K 567"
54
+ #
55
+ # @example Unit preferences
56
+ # HumanNumber.human_number(1000000, abbr_units: true) #=> "1M"
57
+ # HumanNumber.human_number(1000000, abbr_units: false) #=> "1 million"
58
+ #
59
+ # @example Minimum thresholds
60
+ # HumanNumber.human_number(5000, min_unit: 10000) #=> "5000"
61
+ # HumanNumber.human_number(50000, min_unit: 10000) #=> "5만"
62
+ #
63
+ # @example Zero trimming
64
+ # HumanNumber.human_number(1000000, trim_zeros: true) #=> "1M"
65
+ # HumanNumber.human_number(1000000, trim_zeros: false) #=> "1.0M"
66
+ #
67
+ # @note Complete mode (max_digits: nil) shows full precision across multiple units
68
+ # @see Formatters::Number.format The underlying formatter implementation
69
+ # @since 1.0.0
70
+ def human_number(number, locale: I18n.locale, **options)
71
+ validate_number_input!(number)
72
+ validate_locale!(locale)
73
+
74
+ final_options = Formatters::Number.default_options.merge(options)
75
+
76
+ Formatters::Number.format(number, locale:, **final_options)
77
+ end
78
+
79
+ # Formats a currency amount using standard Rails currency formatting.
80
+ #
81
+ # This method applies currency-specific precision rules (e.g., 2 decimals for USD,
82
+ # 0 decimals for JPY/KRW) based on the currency's native locale, ensuring
83
+ # consistent precision regardless of the display locale.
84
+ #
85
+ # @param number [Numeric] The amount to format
86
+ # @param currency_code [String] ISO 4217 currency code (e.g., 'USD', 'EUR', 'KRW', 'JPY')
87
+ # @param locale [Symbol, String] Display locale for formatting rules
88
+ #
89
+ # @return [String] The formatted currency string
90
+ #
91
+ # @example Standard currency formatting
92
+ # HumanNumber.currency(1234.56, currency_code: 'USD', locale: :en) #=> "$1,234.56"
93
+ # HumanNumber.currency(50000, currency_code: 'KRW', locale: :ko) #=> "50,000원"
94
+ # HumanNumber.currency(1234.99, currency_code: 'JPY', locale: :ja) #=> "1,235円"
95
+ #
96
+ # @example Cross-locale precision consistency
97
+ # # USD always shows 2 decimals regardless of display locale
98
+ # HumanNumber.currency(1234.56, currency_code: 'USD', locale: :ko) #=> "$1,234.56"
99
+ #
100
+ # # JPY always shows 0 decimals regardless of display locale
101
+ # HumanNumber.currency(1234.56, currency_code: 'JPY', locale: :en) #=> "1,235円"
102
+ #
103
+ # @note Precision is determined by currency's native locale, not display locale
104
+ # @see Formatters::Number.format_with_currency_precision Currency precision logic
105
+ # @see Formatters::Currency.format Currency symbol and format application
106
+ # @since 1.0.0
107
+ def currency(number, currency_code:, locale:)
108
+ validate_number_input!(number)
109
+ validate_currency_code!(currency_code)
110
+ validate_locale!(locale)
111
+
112
+ formatted_number = Formatters::Number.format_with_currency_precision(number, currency_code:, locale:)
113
+ Formatters::Currency.format(formatted_number, currency_code:, locale:)
114
+ end
115
+
116
+ # Formats a currency amount with intelligent, locale-aware abbreviations.
117
+ #
118
+ # This method combines human-readable number formatting with currency symbols,
119
+ # providing culturally-appropriate abbreviations while maintaining currency
120
+ # symbol and format conventions.
121
+ #
122
+ # @param number [Numeric] The amount to format
123
+ # @param currency_code [String] ISO 4217 currency code (e.g., 'USD', 'EUR', 'KRW', 'JPY')
124
+ # @param locale [Symbol, String] Locale for formatting and unit system selection
125
+ #
126
+ # @option options [Boolean] :abbr_units (true) Use abbreviated unit symbols (K/M/B)
127
+ # vs full words. Only affects Western locales
128
+ # @option options [Integer, nil] :max_digits (2) Maximum significant digits to display.
129
+ # When nil, displays complete amount using multiple units without decimals
130
+ # @option options [Integer, nil] :min_unit (nil) Minimum unit threshold for abbreviation.
131
+ # Amounts below this won't be abbreviated
132
+ # @option options [Boolean] :trim_zeros (true) Remove trailing decimal zeros.
133
+ # Only applies in abbreviated mode (when max_digits is set)
134
+ #
135
+ # @return [String] The formatted currency string with abbreviations
136
+ #
137
+ # @example Basic usage
138
+ # HumanNumber.human_currency(1234567, currency_code: 'USD', locale: :en) #=> "$1.2M"
139
+ # HumanNumber.human_currency(50000, currency_code: 'KRW', locale: :ko) #=> "5만원"
140
+ #
141
+ # @example Precision control
142
+ # HumanNumber.human_currency(1234567, currency_code: 'USD', locale: :en, max_digits: 2) #=> "$1.23M"
143
+ # HumanNumber.human_currency(1234567, currency_code: 'USD', locale: :en, max_digits: nil) #=> "$1M 234K 567"
144
+ #
145
+ # @example Unit preferences
146
+ # HumanNumber.human_currency(1000000, currency_code: 'USD', locale: :en, abbr_units: true) #=> "$1M"
147
+ # HumanNumber.human_currency(1000000, currency_code: 'USD', locale: :en, abbr_units: false) #=> "$1 million"
148
+ #
149
+ # @example Cultural unit systems
150
+ # HumanNumber.human_currency(12345678, currency_code: 'JPY', locale: :ja) #=> "1234.6万円"
151
+ # HumanNumber.human_currency(10000000, currency_code: 'INR', locale: :hi) #=> "1 crore₹"
152
+ #
153
+ # @note Combines human number formatting with currency-specific symbol placement
154
+ # @see #human_number For detailed number formatting options
155
+ # @see #currency For standard currency formatting without abbreviations
156
+ # @since 1.0.0
157
+ def human_currency(number, currency_code:, locale:, **options)
158
+ validate_number_input!(number)
159
+ validate_currency_code!(currency_code)
160
+ validate_locale!(locale)
161
+
162
+ final_options = Formatters::Number.default_currency_number_options.merge(options)
163
+
164
+ formatted_number = Formatters::Number.format(number, locale:, **final_options)
165
+ Formatters::Currency.format(formatted_number, currency_code:, locale:)
166
+ end
167
+
168
+ private
169
+
170
+ # Input validation methods - minimal validation for stability
171
+ def validate_number_input!(number)
172
+ # Only check for the most basic issues that would cause runtime errors
173
+ return if number.respond_to?(:to_f)
174
+
175
+ raise ArgumentError, "Number must be numeric, got #{number.class}"
176
+ end
177
+
178
+ def validate_currency_code!(currency_code)
179
+ # Basic validation only - let downstream handle specifics
180
+ return unless currency_code.nil? || (currency_code.respond_to?(:empty?) && currency_code.empty?)
181
+
182
+ raise ArgumentError, "Currency code cannot be nil or empty"
183
+ end
184
+
185
+ def validate_locale!(locale)
186
+ # Very minimal validation - just ensure it's not something that would break
187
+ return if locale.nil?
188
+
189
+ return if locale.respond_to?(:to_sym)
190
+
191
+ raise ArgumentError, "Locale must be convertible to symbol, got #{locale.class}"
192
+ end
193
+ end
194
+ end
195
+
196
+ require_relative "human_number/railtie" if defined?(Rails::Railtie)
metadata ADDED
@@ -0,0 +1,126 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: human_number
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Your Name
8
+ bindir: exe
9
+ cert_chain: []
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
+ dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: actionview
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - ">="
17
+ - !ruby/object:Gem::Version
18
+ version: '5.2'
19
+ type: :runtime
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - ">="
24
+ - !ruby/object:Gem::Version
25
+ version: '5.2'
26
+ - !ruby/object:Gem::Dependency
27
+ name: i18n
28
+ requirement: !ruby/object:Gem::Requirement
29
+ requirements:
30
+ - - ">="
31
+ - !ruby/object:Gem::Version
32
+ version: '1.6'
33
+ - - "<"
34
+ - !ruby/object:Gem::Version
35
+ version: '2'
36
+ type: :runtime
37
+ prerelease: false
38
+ version_requirements: !ruby/object:Gem::Requirement
39
+ requirements:
40
+ - - ">="
41
+ - !ruby/object:Gem::Version
42
+ version: '1.6'
43
+ - - "<"
44
+ - !ruby/object:Gem::Version
45
+ version: '2'
46
+ - !ruby/object:Gem::Dependency
47
+ name: rails-i18n
48
+ requirement: !ruby/object:Gem::Requirement
49
+ requirements:
50
+ - - ">="
51
+ - !ruby/object:Gem::Version
52
+ version: '5.1'
53
+ type: :runtime
54
+ prerelease: false
55
+ version_requirements: !ruby/object:Gem::Requirement
56
+ requirements:
57
+ - - ">="
58
+ - !ruby/object:Gem::Version
59
+ version: '5.1'
60
+ description: HumanNumber implements accurate number formatting based on international
61
+ standards including Microsoft Globalization documentation and Unicode CLDR. Provides
62
+ human-readable number formats with precise locale-specific decimal separators, thousand
63
+ separators, currency formats, and cultural number conventions.
64
+ email:
65
+ - your.email@example.com
66
+ executables: []
67
+ extensions: []
68
+ extra_rdoc_files: []
69
+ files:
70
+ - ".rspec"
71
+ - ".rubocop.yml"
72
+ - CHANGELOG.md
73
+ - CLAUDE.md
74
+ - Gemfile
75
+ - LICENSE
76
+ - README.md
77
+ - Rakefile
78
+ - config/locales/bn.yml
79
+ - config/locales/de.yml
80
+ - config/locales/en-GB.yml
81
+ - config/locales/en-IN.yml
82
+ - config/locales/en.yml
83
+ - config/locales/es.yml
84
+ - config/locales/fr.yml
85
+ - config/locales/hi.yml
86
+ - config/locales/ja.yml
87
+ - config/locales/ko.yml
88
+ - config/locales/ur.yml
89
+ - config/locales/zh-CN.yml
90
+ - config/locales/zh-TW.yml
91
+ - config/locales/zh.yml
92
+ - human_number.gemspec
93
+ - lib/human_number.rb
94
+ - lib/human_number/formatters/currency.rb
95
+ - lib/human_number/formatters/number.rb
96
+ - lib/human_number/locale_support.rb
97
+ - lib/human_number/rails/helpers.rb
98
+ - lib/human_number/railtie.rb
99
+ - lib/human_number/version.rb
100
+ homepage: https://github.com/yourusername/human_number
101
+ licenses:
102
+ - MIT
103
+ metadata:
104
+ allowed_push_host: https://rubygems.org
105
+ homepage_uri: https://github.com/yourusername/human_number
106
+ source_code_uri: https://github.com/yourusername/human_number
107
+ changelog_uri: https://github.com/yourusername/human_number/blob/main/CHANGELOG.md
108
+ rubygems_mfa_required: 'true'
109
+ rdoc_options: []
110
+ require_paths:
111
+ - lib
112
+ required_ruby_version: !ruby/object:Gem::Requirement
113
+ requirements:
114
+ - - ">="
115
+ - !ruby/object:Gem::Version
116
+ version: 3.1.0
117
+ required_rubygems_version: !ruby/object:Gem::Requirement
118
+ requirements:
119
+ - - ">="
120
+ - !ruby/object:Gem::Version
121
+ version: '0'
122
+ requirements: []
123
+ rubygems_version: 3.6.9
124
+ specification_version: 4
125
+ summary: International standard-based number formatting for Ruby applications
126
+ test_files: []