lib/twitter_cldr/data_readers/number_data_reader.rb
# encoding: UTF-8
# Copyright 2012 Twitter, Inc
# http://www.apache.org/licenses/LICENSE-2.0
module TwitterCldr
module DataReaders
class NumberDataReader < DataReader
PluralRules = TwitterCldr::Formatters::Plurals::Rules
DEFAULT_NUMBER_SYSTEM = :default
ABBREVIATED_MIN_POWER = 3
ABBREVIATED_MAX_POWER = 14
NUMBER_MIN = 10 ** ABBREVIATED_MIN_POWER
NUMBER_MAX = 10 ** (ABBREVIATED_MAX_POWER + 1)
PATTERN_PATH = [:numbers, :formats]
SYMBOL_PATH = [:numbers, :symbols]
TYPES = [:default, :decimal, :currency, :percent]
FORMATS = [:long, :short, :default]
DEFAULT_TYPE = :decimal
DEFAULT_FORMAT = :default
DEFAULT_SIGN = :positive
FORMATTERS = {
decimal: TwitterCldr::Formatters::DecimalFormatter,
currency: TwitterCldr::Formatters::CurrencyFormatter,
percent: TwitterCldr::Formatters::PercentFormatter
}
attr_reader :type, :format, :number_system
def self.types
TYPES
end
def initialize(locale, options = {})
super(locale)
@type = options[:type] || DEFAULT_TYPE
unless type && TYPES.include?(type.to_sym)
raise ArgumentError.new("Type #{type} is not supported")
end
@format = options[:format] || DEFAULT_FORMAT
@number_system = options[:number_system] || default_number_system
end
def format_number(number, options = {})
precision = options[:precision] || 0
pattern_for_number = pattern(number, precision == 0)
options[:locale] = self.locale
tokens = tokenizer.tokenize(pattern_for_number)
formatter.format(tokens, number, options)
end
def pattern(number, decimal = true)
zeroes = number.to_i.abs.to_s.size - 1
magnitude = "1#{'0' * zeroes}"
truncated_num = formatter.truncate_number(number, zeroes % 3 + 1)
truncated_num = truncated_num.to_i if decimal
plural_rule = PluralRules.rule_for(truncated_num, locale)
path = PATTERN_PATH + [
type,
number_system,
[format, :default],
magnitude.to_sym,
[plural_rule, :other]
]
sign = number < 0 ? :negative : :positive
pattern_for_sign(
traverse_finding_best_fit(path, []), sign
)
end
def symbols
@symbols ||= traverse_following_aliases(SYMBOL_PATH + [number_system])
end
def tokenizer
@tokenizer ||= TwitterCldr::Tokenizers::NumberTokenizer.new(self)
end
def formatter
@formatter ||= FORMATTERS[type].new(self)
end
def default_number_system
@default_number_system ||= resource[:numbers][:default_number_systems][:default].to_sym
end
def pattern_for_sign(pattern, sign)
if pattern.include?(";")
positive, negative = pattern.split(";")
sign == :positive ? positive : negative
else
case sign
when :negative
"#{symbols[:minus_sign] || '-'}#{pattern}"
else
pattern
end
end
end
private
def traverse_finding_best_fit(path_pattern, path, hash = resource)
if path_pattern.empty?
result = traverse_following_aliases(path, hash)
return result if result.is_a?(String)
else
Array(path_pattern.first).each do |leg|
result = traverse_finding_best_fit(path_pattern[1..-1], path + [leg], hash)
return result if result
end
result = traverse_following_aliases(path, hash)
return result if result.is_a?(String)
end
end
def traverse_following_aliases(path, hash = resource)
traverse(path, hash) do |_leg, leg_data|
if leg_data.is_a?(Symbol) && leg_data.to_s.start_with?('numbers.')
traverse_following_aliases(leg_data.to_s.split('.').map(&:to_sym))
else
leg_data
end
end
end
def resource
@resource ||= begin
raw = TwitterCldr.get_locale_resource(locale, :numbers)
raw[TwitterCldr.convert_locale(locale)]
end
end
def self.within_abbreviation_range?(number)
abs_value = number.abs
NUMBER_MIN <= abs_value && abs_value < NUMBER_MAX
end
end
end
end