HumanNumber
HumanNumber is a Ruby gem that implements accurate number formatting based on international standards. It references Microsoft Globalization documentation and Unicode CLDR standards to provide human-readable number formats that respect the unique formatting conventions of each country and region.
Features
- π International Standards Compliance: Based on Microsoft Globalization and Unicode CLDR standards
- π’ Cultural Number Systems: Automatic selection of Western (K/M/B/T), East Asian (λ§/μ΅/μ‘°), or Indian (lakh/crore) systems
- π° Currency Formatting: ISO 4217 currency codes with native locale precision rules
- ποΈ Intelligent Abbreviations: Culturally-appropriate large number simplification
- π§ Rails Integration: Complete compatibility with Rails I18n infrastructure
- π rails-i18n Based: Leverages verified locale data
Quick Start
Add to your Gemfile and install:
gem 'human_number'
bundle install
Start formatting numbers:
require 'human_number'
# Human-readable numbers
HumanNumber.human_number(1_234_567) #=> "1.2M"
HumanNumber.human_number(50_000, locale: :ko) #=> "5λ§"
# Currency formatting
HumanNumber.currency(1234.56, currency_code: 'USD', locale: :en) #=> "$1,234.56"
HumanNumber.human_currency(1_234_567, currency_code: 'USD', locale: :en) #=> "$1M"
Installation
Add this line to your application's Gemfile:
gem 'human_number'
And then execute:
$ bundle install
Or install it yourself as:
$ gem install human_number
API Reference
Core Methods
HumanNumber.human_number(number, **options)
Formats numbers with intelligent, culturally-appropriate abbreviations.
# Basic usage
HumanNumber.human_number(1_234_567) #=> "1.2M"
HumanNumber.human_number(50_000, locale: :ko) #=> "5λ§"
HumanNumber.human_number(50_000, locale: :ja) #=> "5δΈ"
# Significant digits control (default: max_digits: 2)
HumanNumber.human_number(1_234_567, max_digits: 1) #=> "1M" # 1 significant digit
HumanNumber.human_number(1_234_567, max_digits: 3) #=> "1.23M" # 3 significant digits
HumanNumber.human_number(1_234_567, max_digits: nil) #=> "1M 234K 567" # Complete breakdown
# Unit preferences (Western locales only)
HumanNumber.human_number(1_000_000, abbr_units: true) #=> "1M"
HumanNumber.human_number(1_000_000, abbr_units: false) #=> "1 million"
# Minimum thresholds
HumanNumber.human_number(5_000, min_unit: 10_000) #=> "5,000"
HumanNumber.human_number(50_000, min_unit: 10_000) #=> "50K"
# Zero trimming (default: true)
HumanNumber.human_number(1_000_000, trim_zeros: true) #=> "1M"
HumanNumber.human_number(1_000_000, trim_zeros: false) #=> "1.0M"
Parameters:
-
locale
(Symbol): Target locale for cultural number systems -
max_digits
(Integer|nil): Maximum significant digits (default: 2, nil for complete mode) -
abbr_units
(Boolean): Use abbreviated vs full units (default: true) -
min_unit
(Integer): Minimum unit threshold for abbreviation (default: nil) -
trim_zeros
(Boolean): Remove trailing decimal zeros (default: true)
HumanNumber.currency(number, currency_code:, locale:)
Standard currency formatting with native locale precision rules.
HumanNumber.currency(1234.56, currency_code: 'USD', locale: :en) #=> "$1,234.56"
HumanNumber.currency(50_000, currency_code: 'KRW', locale: :ko) #=> "50,000μ"
HumanNumber.currency(1234.99, currency_code: 'JPY', locale: :ja) #=> "1,235ε"
# Cross-locale consistency (USD always 2 decimals, JPY always 0)
HumanNumber.currency(1234.56, currency_code: 'USD', locale: :ko) #=> "$1,234.56"
HumanNumber.currency(1234.56, currency_code: 'JPY', locale: :en) #=> "1,235ε"
HumanNumber.human_currency(number, currency_code:, locale:, **options)
Human-readable currency formatting with cultural abbreviations.
HumanNumber.human_currency(1_234_567, currency_code: 'USD', locale: :en) #=> "$1M"
HumanNumber.human_currency(50_000, currency_code: 'KRW', locale: :ko) #=> "5λ§μ"
# Combined options
HumanNumber.human_currency(1_234_567, currency_code: 'USD', locale: :en, max_digits: 3) #=> "$1.23M"
HumanNumber.human_currency(1_234_567, currency_code: 'USD', locale: :en, max_digits: nil) #=> "$1M 234K 567"
Rails Integration
In Rails applications, helper methods are automatically available:
<%= human_number(1_234_567) %> <!-- 1.2M -->
<%= human_currency(1_234_567, currency_code: 'USD') %> <!-- $1M -->
<%= currency(1234.56, currency_code: 'USD') %> <!-- $1,234.56 -->
<!-- With options -->
<%= human_number(1_234_567, max_digits: 3, locale: :ko) %> <!-- 123λ§ -->
<%= number_to_human_size(1_234_567) %> <!-- 1.2M (legacy) -->
Cultural Number Systems
Different cultures have fundamentally different concepts for large numbers:
Western System (K/M/B/T)
Used by: English, German, French, Spanish, Italian, Portuguese, Russian, Dutch, Swedish, Danish, Norwegian
HumanNumber.human_number(1_234_567, locale: :en) #=> "1.2M"
HumanNumber.human_number(1_000_000_000, locale: :en) #=> "1B"
Units: thousand (1,000), million (1,000,000), billion (1,000,000,000), trillion (1,000,000,000,000)
East Asian System (ε/δΈ/ε/ε )
Used by: Korean (ko), Japanese (ja), Chinese (zh, zh-CN, zh-TW)
HumanNumber.human_number(1_234_567, locale: :ko) #=> "120λ§"
HumanNumber.human_number(1_234_567, locale: :ja) #=> "120δΈ"
HumanNumber.human_number(100_000_000, locale: :ko) #=> "1μ΅"
# Complete mode shows cultural spacing differences
HumanNumber.human_number(12_345_678, locale: :ko, max_digits: nil) #=> "1234λ§ 5678" # Korean: spaces
HumanNumber.human_number(12_345_678, locale: :ja, max_digits: nil) #=> "1234δΈ5678" # Japanese: no spaces
Units: λ§/δΈ (ten thousand), μ΅/ε (hundred million), μ‘°/ε
(trillion)
Cultural spacing: Japanese uses no spaces between units, Korean/Chinese use spaces (configurable via locale files)
Indian System (lakh/crore)
Used by: Hindi (hi), Urdu (ur), Bengali (bn), Indian English (en-IN)
HumanNumber.human_number(100_000, locale: :hi) #=> "1 lakh"
HumanNumber.human_number(10_000_000, locale: :hi) #=> "1 crore"
Units: thousand (1,000), lakh (100,000), crore (10,000,000)
The gem automatically selects the appropriate system based on locale, ensuring cultural accuracy.
Architecture & Design Philosophy
Direct Method Design
HumanNumber uses direct class methods rather than instance-based objects for simplicity:
# Direct approach (current)
HumanNumber.human_number(1234567, locale: :ko, max_digits: 2)
# Instance approach (rejected)
formatter = HumanNumber::Formatter.new(locale: :ko, max_digits: 2)
formatter.format(1234567)
Why Direct Methods?
- Simplicity: Clean, straightforward API
- Thread Safety: No shared mutable state
- Rails Compatibility: Matches Rails helper patterns
Separation of Concerns
HumanNumber uses a two-stage formatting process:
- Number Formatting: Converts numbers to human-readable strings with cultural units
- Currency Application: Applies currency symbols and format strings
# Internal flow for HumanNumber.human_currency(1234567, currency_code: 'USD', locale: :en)
formatted_number = Formatters::Number.format(1234567, locale: :en, max_digits: 2) #=> "1.2M"
final_result = Formatters::Currency.format("1.2M", currency_code: 'USD', locale: :en) #=> "$1.2M"
This separation enables:
- Independent testing of number vs currency logic
- Reusability of number formatting for non-currency contexts
- Maintainability when adding new currencies or number systems
Centralized Locale Logic
Currency-locale relationships are managed centrally in LocaleSupport
:
CURRENCY_NATIVE_LOCALES = {
"USD" => [:en], "EUR" => %i[de fr es it nl],
"KRW" => [:ko], "JPY" => [:ja]
# ... 40+ currencies
}
This ensures:
- Consistent precision rules: USD always shows 2 decimals, JPY shows 0
- Smart fallbacks: When user locale doesn't match currency's native locale
- Easy maintenance: New currencies require only one mapping entry
Configuration-Free Design
HumanNumber deliberately avoids runtime configuration in favor of explicit parameters:
# No global configuration
HumanNumber.human_number(1234567, locale: :ko, max_digits: 2)
# Application defaults handled at application level
class ApplicationController
def format_money(amount, currency)
HumanNumber.human_currency(amount, currency_code: currency, locale: I18n.locale, max_digits: 2)
end
end
Benefits:
- No shared state or thread safety concerns
- Predictable behavior (each call is independent)
- Easier testing (no configuration setup/teardown)
Development
After checking out the repo:
$ cd human_number
$ bundle install
$ rake spec
Run tests:
$ rake spec
Run linting:
$ rake rubocop
Run all quality checks:
$ rake # Runs both spec and rubocop
Contributing
- Fork it (https://github.com/ether-moon/human_number/fork)
- Create your feature branch (
git checkout -b feature/my-new-feature
) - Commit your changes (
git commit -am 'Add some feature'
) - Push to the branch (
git push origin feature/my-new-feature
) - Create a new Pull Request
Contribution Guidelines
- Please write tests when adding new features
- Follow RuboCop style guidelines
- Write clear and descriptive commit messages
- Update documentation for API changes
- Ensure 100% test coverage for new features
Adding New Locales
To add support for a new locale:
- Add locale to appropriate number system in
lib/human_number/formatters/number.rb
- Create locale file in
config/locales/
with unit translations - Add currency mapping to
CURRENCY_NATIVE_LOCALES
if applicable - Add comprehensive tests
License
MIT License. See the LICENSE file for details.
Standards Reference
This project is based on the following international standards and documentation:
- Microsoft Globalization - Number Formatting - Cultural number formatting standards
- Microsoft Globalization - Currency Formats - Currency formatting standards
- Unicode CLDR - Locale data standards
- ISO 4217 - Currency code standards
- rails-i18n - Rails internationalization support
Changelog
See CHANGELOG.md for details.
Issues
Please report bugs and feature requests at GitHub Issues.