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.
- 🌍 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
Add to your Gemfile and install:
gem 'human_number'bundle installStart 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"Add this line to your application's Gemfile:
gem 'human_number'And then execute:
$ bundle installOr install it yourself as:
$ gem install human_numberFormats 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 systemsmax_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)
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円"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"Determines which number system is used for a given locale. Returns a symbol indicating the cultural number formatting system.
# Western/Default system (K/M/B/T)
HumanNumber.number_system(locale: :en) #=> :default
HumanNumber.number_system(locale: :fr) #=> :default
HumanNumber.number_system(locale: :de) #=> :default
# East Asian system (만/억/조 or 万/億/兆)
HumanNumber.number_system(locale: :ko) #=> :east_asian
HumanNumber.number_system(locale: :ja) #=> :east_asian
HumanNumber.number_system(locale: :zh) #=> :east_asian
# Indian system (thousand/lakh/crore)
HumanNumber.number_system(locale: :hi) #=> :indian
HumanNumber.number_system(locale: :'en-IN') #=> :indian
# Defaults to current I18n.locale when not specified
HumanNumber.number_system #=> (uses I18n.locale)Return Values:
:default- Western system using K/M/B/T (thousand/million/billion/trillion):east_asian- Asian system using 만/억/조 or 万/億/兆 patterns:indian- South Asian system using thousand/lakh/crore
Determines which number system is used for a given currency code. Returns a symbol indicating the culturally appropriate formatting system for that currency's region.
# Default system currencies
HumanNumber.currency_number_system(currency_code: 'USD') #=> :default
HumanNumber.currency_number_system(currency_code: 'EUR') #=> :default
HumanNumber.currency_number_system(currency_code: 'GBP') #=> :default
# East Asian system currencies
HumanNumber.currency_number_system(currency_code: 'KRW') #=> :east_asian # Korean Won
HumanNumber.currency_number_system(currency_code: 'JPY') #=> :east_asian # Japanese Yen
HumanNumber.currency_number_system(currency_code: 'CNY') #=> :east_asian # Chinese Yuan
# Indian system currencies
HumanNumber.currency_number_system(currency_code: 'INR') #=> :indian # Indian Rupee
# Case-insensitive and handles whitespace
HumanNumber.currency_number_system(currency_code: 'krw') #=> :east_asian
HumanNumber.currency_number_system(currency_code: ' USD ') #=> :defaultPractical Usage: These methods are useful for understanding which formatting system will be applied, or for building custom formatting logic:
# Check system before formatting
system = HumanNumber.number_system(locale: :ko)
if system == :east_asian
puts "Will use 만/억/조 units"
result = HumanNumber.human_number(50_000, locale: :ko) #=> "5만"
end
# Currency-aware system detection
currency_system = HumanNumber.currency_number_system(currency_code: 'KRW')
locale_system = HumanNumber.number_system(locale: :ko)
puts "Consistent systems!" if currency_system == locale_system #=> trueIn 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) -->Different cultures have fundamentally different concepts for large numbers:
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)
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 spacesUnits: 만/万 (ten thousand), 억/億 (hundred million), 조/兆 (trillion)
Cultural spacing: Japanese uses no spaces between units, Korean/Chinese use spaces (configurable via locale files)
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.
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
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
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
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
endBenefits:
- No shared state or thread safety concerns
- Predictable behavior (each call is independent)
- Easier testing (no configuration setup/teardown)
After checking out the repo:
$ cd human_number
$ bundle install
$ rake specRun tests:
$ rake specRun linting:
$ rake rubocopRun all quality checks:
$ rake # Runs both spec and rubocop- 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
- 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
To add support for a new locale:
- Add locale to appropriate system mapping in
lib/human_number/number_system.rb:- Update
LOCALE_SYSTEM_MAPPINGto include the new locale - If needed, add currency mapping to
CURRENCY_SYSTEM_MAPPING
- Update
- Create locale file in
config/locales/with unit translations following rails-i18n structure - Add currency precision mapping to
CURRENCY_NATIVE_LOCALESinlib/human_number/locale_support.rbif applicable - Add comprehensive tests covering the new locale in both
spec/human_number_spec.rbandspec/human_number/number_system_spec.rb
Example: Adding Portuguese (Brazil) support:
# In lib/human_number/number_system.rb
LOCALE_SYSTEM_MAPPING = {
east_asian: %i[ko ja zh zh-CN zh-TW].freeze,
indian: %i[hi ur bn en-IN].freeze,
# Add pt-BR to default system (no entry needed)
}.freeze
# In config/locales/pt-BR.yml
pt-BR:
number:
human:
decimal_units:
abbr_units:
thousand: "K"
million: "M"
billion: "B"
trillion: "T"MIT License. See the LICENSE file for details.
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
See CHANGELOG.md for details.
Please report bugs and feature requests at GitHub Issues.