ruby-cldr 0.0.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 (80) hide show
  1. data/LICENSE +20 -0
  2. data/README.textile +104 -0
  3. data/Rakefile +18 -0
  4. data/TODO +68 -0
  5. data/VERSION +1 -0
  6. data/cldr.thor +5 -0
  7. data/lib/cldr/data/base.rb +66 -0
  8. data/lib/cldr/data/calendars/gregorian.rb +124 -0
  9. data/lib/cldr/data/calendars.rb +12 -0
  10. data/lib/cldr/data/currencies.rb +26 -0
  11. data/lib/cldr/data/delimiters.rb +21 -0
  12. data/lib/cldr/data/languages.rb +17 -0
  13. data/lib/cldr/data/numbers.rb +60 -0
  14. data/lib/cldr/data/plurals/cldr_grammar.treetop +50 -0
  15. data/lib/cldr/data/plurals/grammar.rb +536 -0
  16. data/lib/cldr/data/plurals/rules.rb +113 -0
  17. data/lib/cldr/data/plurals.rb +39 -0
  18. data/lib/cldr/data/territories.rb +17 -0
  19. data/lib/cldr/data/timezones.rb +25 -0
  20. data/lib/cldr/data/units.rb +25 -0
  21. data/lib/cldr/data.rb +34 -0
  22. data/lib/cldr/download.rb +20 -0
  23. data/lib/cldr/export/ruby.rb +16 -0
  24. data/lib/cldr/export/yaml.rb +39 -0
  25. data/lib/cldr/export.rb +69 -0
  26. data/lib/cldr/format/currency.rb +11 -0
  27. data/lib/cldr/format/date.rb +144 -0
  28. data/lib/cldr/format/datetime/base.rb +32 -0
  29. data/lib/cldr/format/datetime.rb +28 -0
  30. data/lib/cldr/format/decimal/base.rb +18 -0
  31. data/lib/cldr/format/decimal/fraction.rb +28 -0
  32. data/lib/cldr/format/decimal/integer.rb +46 -0
  33. data/lib/cldr/format/decimal/number.rb +44 -0
  34. data/lib/cldr/format/decimal.rb +32 -0
  35. data/lib/cldr/format/percent.rb +11 -0
  36. data/lib/cldr/format/time.rb +71 -0
  37. data/lib/cldr/format.rb +113 -0
  38. data/lib/cldr/thor.rb +33 -0
  39. data/lib/cldr.rb +7 -0
  40. data/lib/core_ext/hash/deep_merge.rb +7 -0
  41. data/lib/core_ext/hash/deep_stringify_keys.rb +11 -0
  42. data/lib/core_ext/hash/symbolize_keys.rb +10 -0
  43. data/lib/core_ext/string/camelize.rb +5 -0
  44. data/lib/core_ext/string/underscore.rb +9 -0
  45. data/test/all.rb +3 -0
  46. data/test/data/all.rb +3 -0
  47. data/test/data/calendars_test.rb +149 -0
  48. data/test/data/currencies_test.rb +47 -0
  49. data/test/data/delimiters_test.rb +31 -0
  50. data/test/data/languages_test.rb +67 -0
  51. data/test/data/numbers_test.rb +61 -0
  52. data/test/data/plurals_test.rb +132 -0
  53. data/test/data/territories_test.rb +51 -0
  54. data/test/data/timezones_test.rb +56 -0
  55. data/test/data/units_test.rb +36 -0
  56. data/test/export_test.rb +57 -0
  57. data/test/formats/all.rb +3 -0
  58. data/test/formats/datetime/all.rb +3 -0
  59. data/test/formats/datetime/date_test.rb +31 -0
  60. data/test/formats/datetime/datetime_test.rb +18 -0
  61. data/test/formats/datetime/day_test.rb +41 -0
  62. data/test/formats/datetime/hour_test.rb +79 -0
  63. data/test/formats/datetime/minute_test.rb +25 -0
  64. data/test/formats/datetime/month_test.rb +76 -0
  65. data/test/formats/datetime/period_test.rb +20 -0
  66. data/test/formats/datetime/quarter_test.rb +118 -0
  67. data/test/formats/datetime/second_test.rb +80 -0
  68. data/test/formats/datetime/time_test.rb +33 -0
  69. data/test/formats/datetime/timezone_test.rb +23 -0
  70. data/test/formats/datetime/year_test.rb +57 -0
  71. data/test/formats/decimal/fraction_test.rb +17 -0
  72. data/test/formats/decimal/integer_test.rb +67 -0
  73. data/test/formats/decimal/number_test.rb +77 -0
  74. data/test/formats/decimal_test.rb +19 -0
  75. data/test/formats/format_test.rb +66 -0
  76. data/test/formats/rails_compat/all.rb +3 -0
  77. data/test/formats/rails_compat/format_integer_test.rb +56 -0
  78. data/test/formats/rails_compat/format_number_test.rb +134 -0
  79. data/test/test_helper.rb +5 -0
  80. metadata +169 -0
@@ -0,0 +1,25 @@
1
+ # I probably don't really get timezones.
2
+
3
+ class Cldr
4
+ module Data
5
+ class Timezones < Base
6
+ def initialize(locale)
7
+ super
8
+ self[:timezones] = timezones
9
+ end
10
+
11
+ def timezones
12
+ @timezones ||= select('dates/timeZoneNames/zone').inject({}) do |result, zone|
13
+ type = zone.attribute('type').value.to_sym
14
+ city = select(zone, 'exemplarCity').first
15
+ result[type] = {}
16
+ # see en.xml, Europe/London, does not have an exemplarCity element
17
+ # instead it has long and short daylight names which otherwise only
18
+ # have metazones. (??)
19
+ result[type][:city] = city.content if city
20
+ result
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,25 @@
1
+ class Cldr
2
+ module Data
3
+ class Units < Base
4
+ def initialize(locale)
5
+ super
6
+ self[:units] = units
7
+ end
8
+
9
+ def units
10
+ select('units/unit').inject({}) do |result, node|
11
+ result[node.attribute('type').value.to_sym] = unit(node)
12
+ result
13
+ end
14
+ end
15
+
16
+ def unit(node)
17
+ node.xpath('unitPattern').inject({}) do |result, node|
18
+ count = node.attribute('count') ? node.attribute('count').value.to_sym : :one
19
+ result[count] = node.content unless draft?(node)
20
+ result
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
data/lib/cldr/data.rb ADDED
@@ -0,0 +1,34 @@
1
+ require 'core_ext/string/camelize'
2
+
3
+ class Cldr
4
+ module Data
5
+ autoload :Base, 'cldr/data/base'
6
+ autoload :Calendars, 'cldr/data/calendars'
7
+ autoload :Currencies, 'cldr/data/currencies'
8
+ autoload :Delimiters, 'cldr/data/delimiters'
9
+ autoload :Languages, 'cldr/data/languages'
10
+ autoload :Numbers, 'cldr/data/numbers'
11
+ autoload :Plurals, 'cldr/data/plurals'
12
+ autoload :Territories, 'cldr/data/territories'
13
+ autoload :Timezones, 'cldr/data/timezones'
14
+ autoload :Units, 'cldr/data/units'
15
+
16
+ class << self
17
+ def dir
18
+ @dir ||= File.expand_path('./vendor/cldr/common')
19
+ end
20
+
21
+ def dir=(dir)
22
+ @dir = dir
23
+ end
24
+
25
+ def locales
26
+ Dir["#{dir}/main/*.xml"].map { |path| path =~ /([\w_-]+)\.xml/ && $1 }
27
+ end
28
+
29
+ def components
30
+ self.constants.sort - [:Base, :Export]
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,20 @@
1
+ require 'fileutils'
2
+ require 'net/http'
3
+ require 'uri'
4
+ require 'tempfile'
5
+
6
+ class Cldr
7
+ class << self
8
+ def download(source = nil, target = nil)
9
+ source ||= 'http://unicode.org/Public/cldr/1.7.2/core.zip'
10
+ target ||= File.expand_path('./vendor/cldr')
11
+
12
+ source = URI.parse(source)
13
+ tempfile = Tempfile.new('cldr-core')
14
+
15
+ system("curl #{source} -o #{tempfile.path}")
16
+ FileUtils.mkdir_p(target)
17
+ system("unzip #{tempfile.path} -d #{target}")
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,16 @@
1
+ class Cldr
2
+ module Export
3
+ class Ruby
4
+ def export(locale, component, options = {})
5
+ data = Export.data(component, locale, options)
6
+ data = data.to_ruby if data.respond_to?(:to_ruby)
7
+ unless data.empty?
8
+ path = Export.path(locale, component, 'rb')
9
+ Export.write(path, data)
10
+ yield(component, locale, path) if block_given?
11
+ data
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,39 @@
1
+ require 'ya2yaml'
2
+
3
+ class Cldr
4
+ module Export
5
+ class Yaml < Ya2YAML
6
+ def initialize
7
+ super(:syck_compatible => true)
8
+ end
9
+
10
+ def export(locale, component, options = {})
11
+ data = Export.data(component, locale, options)
12
+ unless data.empty?
13
+ data = { locale.to_s.gsub('_', '-') => data }.deep_stringify_keys
14
+ path = Export.path(locale, component, 'yml')
15
+ Export.write(path, yaml(data))
16
+ yield(component, locale, path) if block_given?
17
+ data
18
+ end
19
+ end
20
+
21
+ def yaml(data)
22
+ emit(data, 1)[1..-1]
23
+ end
24
+
25
+ def emit(object, level = 1)
26
+ result = object.is_a?(Symbol) ? object.inspect : super
27
+ result.gsub(/(\s{1})(no):/i) { %(#{$1}"#{$2}":) } # FIXME fucking spaghetti code
28
+ end
29
+
30
+ def is_one_plain_line?(str)
31
+ # removed REX_BOOL, REX_INT
32
+ str !~ /^([\-\?:,\[\]\{\}\#&\*!\|>'"%@`\s]|---|\.\.\.)/ &&
33
+ str !~ /[:\#\s\[\]\{\},]/ &&
34
+ str !~ /#{REX_ANY_LB}/ &&
35
+ str !~ /^(#{REX_FLOAT}|#{REX_MERGE}|#{REX_NULL}|#{REX_TIMESTAMP}|#{REX_VALUE})$/x
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,69 @@
1
+ require 'i18n'
2
+ require 'i18n/locale/fallbacks'
3
+ require 'core_ext/string/camelize'
4
+ require 'core_ext/string/underscore'
5
+ require 'core_ext/hash/deep_stringify_keys'
6
+
7
+ class Cldr
8
+ module Export
9
+ autoload :Ruby, 'cldr/export/ruby'
10
+ autoload :Yaml, 'cldr/export/yaml'
11
+
12
+ class << self
13
+ def base_path
14
+ @@base_path ||= File.expand_path('./data')
15
+ end
16
+
17
+ def base_path=(base_path)
18
+ @@base_path = base_path
19
+ end
20
+
21
+ def export(options = {}, &block)
22
+ locales = options[:locales] || Data.locales
23
+ components = options[:components] || Data.components
24
+ self.base_path = options[:target] if options[:target]
25
+
26
+ locales.each do |locale|
27
+ components.each do |component|
28
+ exporter(component, options[:format]).export(locale, component, options, &block)
29
+ end
30
+ end
31
+ end
32
+
33
+ def exporter(component, format)
34
+ name = format ? format : component.to_s == 'Plurals' ? 'ruby' : 'yaml'
35
+ const_get(name.to_s.camelize).new
36
+ end
37
+
38
+ def data(component, locale, options = {})
39
+ if component.to_s == 'Plurals'
40
+ Data.const_get(component.to_s.camelize).new(locale)
41
+ else
42
+ data = locales(locale, options).inject({}) do |result, locale|
43
+ data = Data.const_get(component.to_s.camelize).new(locale)
44
+ data ? data.deep_merge(result) : result
45
+ end
46
+ # data = resolve_links if options[:merge] TODO!!
47
+ data
48
+ end
49
+ end
50
+
51
+ def locales(locale, options)
52
+ locale = locale.to_s.gsub('_', '-')
53
+ locales = options[:merge] ? I18n::Locale::Fallbacks.new[locale.to_sym] : [locale.to_sym]
54
+ locales << :root
55
+ locales
56
+ end
57
+
58
+ def write(path, data)
59
+ FileUtils.rm(path) if File.exists?(path)
60
+ FileUtils.mkdir_p(File.dirname(path))
61
+ File.open(path, 'w+') { |f| f.write(data) }
62
+ end
63
+
64
+ def path(locale, component, extension)
65
+ "#{Export.base_path}/#{locale.to_s.gsub('_', '-')}/#{component.to_s.underscore}.#{extension}"
66
+ end
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,11 @@
1
+ # encoding: utf-8
2
+
3
+ class Cldr
4
+ module Format
5
+ class Currency < Decimal
6
+ def apply(number, options = {})
7
+ super.gsub('¤', options[:currency] || '$')
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,144 @@
1
+ class Cldr
2
+ module Format
3
+ class Date < Datetime::Base
4
+ PATTERN = /G{1,5}|y+|Y+|Q{1,4}|q{1,5}|M{1,5}|L{1,5}|d{1,2}|F{1}|E{1,5}|e{1,5}|c{1,5}/
5
+ METHODS = { # ignoring u, l, g, j, A
6
+ 'G' => :era,
7
+ 'y' => :year,
8
+ 'Y' => :year_of_week_of_year,
9
+ 'Q' => :quarter,
10
+ 'q' => :quarter_stand_alone,
11
+ 'M' => :month,
12
+ 'L' => :month_stand_alone,
13
+ 'w' => :week_of_year,
14
+ 'W' => :week_of_month,
15
+ 'd' => :day,
16
+ 'D' => :day_of_month,
17
+ 'F' => :day_of_week_in_month,
18
+ 'E' => :weekday,
19
+ 'e' => :weekday_local,
20
+ 'c' => :weekday_local_stand_alone,
21
+ }
22
+
23
+ def era(date, pattern, length)
24
+ raise 'not implemented'
25
+ end
26
+
27
+ def year(date, pattern, length)
28
+ year = date.year.to_s
29
+ year = year.length == 1 ? year : year[-2, 2] if length == 2
30
+ year = year.rjust(length, '0') if length > 1
31
+ year
32
+ end
33
+
34
+ def year_of_week_of_year(date, pattern, length)
35
+ raise 'not implemented'
36
+ end
37
+
38
+ def day_of_week_in_month(date, pattern, length) # e.g. 2nd Wed in July
39
+ raise 'not implemented'
40
+ end
41
+
42
+ def quarter(date, pattern, length)
43
+ quarter = (date.month.to_i - 1) / 3 + 1
44
+ case length
45
+ when 1
46
+ quarter.to_s
47
+ when 2
48
+ quarter.to_s.rjust(length, '0')
49
+ when 3
50
+ calendar[:quarters][:format][:abbreviated][quarter]
51
+ when 4
52
+ calendar[:quarters][:format][:wide][quarter]
53
+ end
54
+ end
55
+
56
+ def quarter_stand_alone(date, pattern, length)
57
+ quarter = (date.month.to_i - 1) / 3 + 1
58
+ case length
59
+ when 1
60
+ quarter.to_s
61
+ when 2
62
+ quarter.to_s.rjust(length, '0')
63
+ when 3
64
+ raise 'not yet implemented (requires cldr\'s "multiple inheritance")'
65
+ # calendar[:quarters][:'stand-alone'][:abbreviated][key]
66
+ when 4
67
+ raise 'not yet implemented (requires cldr\'s "multiple inheritance")'
68
+ # calendar[:quarters][:'stand-alone'][:wide][key]
69
+ when 5
70
+ calendar[:quarters][:'stand-alone'][:narrow][quarter]
71
+ end
72
+ end
73
+
74
+ def month(date, pattern, length)
75
+ case length
76
+ when 1
77
+ date.month.to_s
78
+ when 2
79
+ date.month.to_s.rjust(length, '0')
80
+ when 3
81
+ calendar[:months][:format][:abbreviated][date.month]
82
+ when 4
83
+ calendar[:months][:format][:wide][date.month]
84
+ when 5
85
+ raise 'not yet implemented (requires cldr\'s "multiple inheritance")'
86
+ calendar[:months][:format][:narrow][date.month]
87
+ else
88
+ # raise unknown date format
89
+ end
90
+ end
91
+
92
+ def month_stand_alone(date, pattern, length)
93
+ case length
94
+ when 1
95
+ date.month.to_s
96
+ when 2
97
+ date.month.to_s.rjust(length, '0')
98
+ when 3
99
+ raise 'not yet implemented (requires cldr\'s "multiple inheritance")'
100
+ calendar[:months][:'stand-alone'][:abbreviated][date.month]
101
+ when 4
102
+ raise 'not yet implemented (requires cldr\'s "multiple inheritance")'
103
+ calendar[:months][:'stand-alone'][:wide][date.month]
104
+ when 5
105
+ calendar[:months][:'stand-alone'][:narrow][date.month]
106
+ else
107
+ # raise unknown date format
108
+ end
109
+ end
110
+
111
+ def day(date, pattern, length)
112
+ case length
113
+ when 1
114
+ date.day.to_s
115
+ when 2
116
+ date.day.to_s.rjust(length, '0')
117
+ end
118
+ end
119
+
120
+ WEEKDAY_KEYS = [:sun, :mon, :tue, :wed, :thu, :fri, :sat]
121
+
122
+ def weekday(date, pattern, length)
123
+ key = WEEKDAY_KEYS[date.wday]
124
+ case length
125
+ when 1..3
126
+ calendar[:days][:format][:abbreviated][key]
127
+ when 4
128
+ calendar[:days][:format][:wide][key]
129
+ when 5
130
+ calendar[:days][:'stand-alone'][:narrow][key]
131
+ end
132
+ end
133
+
134
+ def weekday_local(date, pattern, length)
135
+ # "Like E except adds a numeric value depending on the local starting day of the week"
136
+ raise 'not implemented (need to defer a country to lookup the local first day of week from weekdata)'
137
+ end
138
+
139
+ def weekday_local_stand_alone(date, pattern, length)
140
+ raise 'not implemented (need to defer a country to lookup the local first day of week from weekdata)'
141
+ end
142
+ end
143
+ end
144
+ end
@@ -0,0 +1,32 @@
1
+ class Cldr
2
+ module Format
3
+ class Datetime
4
+ class Base
5
+ attr_reader :calendar
6
+
7
+ def initialize(format, calendar)
8
+ @calendar = calendar
9
+ compile(format)
10
+ end
11
+
12
+ protected
13
+
14
+ def compile(format)
15
+ (class << self; self; end).class_eval <<-code
16
+ def apply(date, options = {}); #{compile_format(format)}; end
17
+ code
18
+ end
19
+
20
+ # compile_format("EEEE, d. MMMM y") # =>
21
+ # '' + weekday(date, "EEEE", 4) + ', ' + day(date, "d", 1) + '. ' +
22
+ # month(date, "MMMM", 4) + ' ' + year(date, "y", 1) + ''
23
+ def compile_format(format)
24
+ "'" + format.gsub(self.class.const_get(:PATTERN)) do |token|
25
+ method = self.class.const_get(:METHODS)[token[0, 1]]
26
+ "' + #{method}(date, #{token.inspect}, #{token.length}) + '"
27
+ end + "'"
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,28 @@
1
+ class Cldr
2
+ module Format
3
+ class Datetime
4
+ autoload :Base, 'cldr/format/datetime/base'
5
+
6
+ attr_reader :date, :time
7
+
8
+ def initialize(format, date, time)
9
+ @date, @time = date, time
10
+ compile(format)
11
+ end
12
+
13
+ protected
14
+
15
+ def compile(format)
16
+ (class << self; self; end).class_eval <<-code
17
+ def apply(datetime, options = {}); #{compile_format(format)}; end
18
+ code
19
+ end
20
+
21
+ def compile_format(format)
22
+ "'" + format.gsub(%r(\{(0|1)\})) do |token|
23
+ "' + #{token == '{0}' ? 'time' : 'date'}.apply(datetime, options) + '"
24
+ end + "'"
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,18 @@
1
+ class Cldr
2
+ module Format
3
+ class Decimal
4
+ class Base
5
+ def interpolate(string, value, orientation = :right)
6
+ value = value.to_s
7
+ length = value.length
8
+ start, pad = orientation == :left ? [0, :rjust] : [-length, :ljust]
9
+
10
+ string = string.dup
11
+ string = string.ljust(length, '#') if string.length < length
12
+ string[start, length] = value
13
+ string.gsub('#', '')
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,28 @@
1
+ class Cldr
2
+ module Format
3
+ class Decimal
4
+ class Fraction < Base
5
+ attr_reader :format, :decimal, :precision
6
+
7
+ def initialize(format, symbols = {})
8
+ @format = format ? format.split('.').pop : ''
9
+ @decimal = symbols[:decimal] || '.'
10
+ @precision = @format.length
11
+ end
12
+
13
+ def apply(fraction, options = {})
14
+ precision = options[:precision] || self.precision
15
+ if precision > 0
16
+ decimal + interpolate(format(options), fraction, :left)
17
+ else
18
+ ''
19
+ end
20
+ end
21
+
22
+ def format(options)
23
+ options[:precision] ? '0' * options[:precision] : @format
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,46 @@
1
+ class Cldr
2
+ module Format
3
+ class Decimal
4
+ class Integer < Base
5
+ attr_reader :format, :separator, :groups
6
+
7
+ def initialize(format, symbols = {})
8
+ format = format.split('.')[0]
9
+ @format = prepare_format(format, symbols)
10
+ @groups = parse_groups(format)
11
+ @separator = symbols[:group] || ','
12
+ end
13
+
14
+ def apply(number, options = {})
15
+ format_groups(interpolate(format, number.to_i))
16
+ end
17
+
18
+ def format_groups(string)
19
+ return string if groups.empty?
20
+ tokens = []
21
+ tokens << chop_group(string, groups.first)
22
+ tokens << chop_group(string, groups.last) while string.length > groups.last
23
+ tokens << string
24
+ tokens.compact.reverse.join(separator)
25
+ end
26
+
27
+ def parse_groups(format)
28
+ return [] unless index = format.rindex(',')
29
+ rest = format[0, index]
30
+ widths = [format.length - index - 1]
31
+ widths << rest.length - rest.rindex(',') - 1 if rest.rindex(',')
32
+ widths.compact.uniq
33
+ end
34
+
35
+ def chop_group(string, size)
36
+ string.slice!(-size, size) if string.length > size
37
+ end
38
+
39
+ def prepare_format(format, symbols)
40
+ signs = symbols.values_at(:plus_sign, :minus_sign)
41
+ format.tr(',', '').tr('+-', signs.join)
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,44 @@
1
+ class Cldr
2
+ module Format
3
+ class Decimal
4
+ class Number
5
+ attr_reader :prefix, :suffix, :integer_format, :fraction_format, :symbols
6
+
7
+ DEFAULT_SYMBOLS = { :group => ',', :decimal => '.', :plus_sign => '+', :minus_sign => '-' }
8
+ FORMAT_PATTERN = /([^0#,\.]*)([0#,\.]+)([^0#,\.]*)$/
9
+
10
+ def initialize(format, symbols = {})
11
+ @symbols = DEFAULT_SYMBOLS.merge(symbols)
12
+ @prefix, @suffix, @integer_format, @fraction_format = *parse_format(format, symbols)
13
+ end
14
+
15
+ def apply(number, options = {})
16
+ int, fraction = parse_number(number, options)
17
+
18
+ result = integer_format.apply(int, options)
19
+ result << fraction_format.apply(fraction, options) if fraction
20
+ prefix + result + suffix
21
+ end
22
+
23
+ protected
24
+
25
+ def parse_format(format, symbols = {})
26
+ format =~ FORMAT_PATTERN
27
+ prefix, suffix, int, fraction = $1.to_s, $3.to_s, *$2.split('.')
28
+ [prefix, suffix, Integer.new(int, symbols), Fraction.new(fraction, symbols)]
29
+ end
30
+
31
+ def parse_number(number, options = {})
32
+ precision = options[:precision] || fraction_format.precision
33
+ number = round_to(number, precision)
34
+ number.abs.to_s.split('.')
35
+ end
36
+
37
+ def round_to(number, precision)
38
+ factor = 10 ** precision
39
+ (number * factor).round.to_f / factor
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,32 @@
1
+ class Cldr
2
+ module Format
3
+ class Decimal
4
+ autoload :Base, 'cldr/format/decimal/base'
5
+ autoload :Fraction, 'cldr/format/decimal/fraction'
6
+ autoload :Integer, 'cldr/format/decimal/integer'
7
+ autoload :Number, 'cldr/format/decimal/number'
8
+
9
+ attr_reader :positive, :negative
10
+
11
+ def initialize(format, symbols = {})
12
+ @positive, @negative = parse_format(format, symbols)
13
+ end
14
+
15
+ def apply(number, options = {})
16
+ number = Float(number)
17
+ format = number.abs == number ? positive : negative
18
+ format.apply(number, options)
19
+ rescue TypeError, ArgumentError
20
+ number
21
+ end
22
+
23
+ protected
24
+
25
+ def parse_format(format, symbols)
26
+ formats = format.split(symbols[:list] || ';')
27
+ formats << "#{symbols[:minus] || '-'}#{format}" if formats.size == 1
28
+ formats.map { |format| Number.new(format, symbols) }
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,11 @@
1
+ # encoding: utf-8
2
+
3
+ class Cldr
4
+ module Format
5
+ class Percent < Decimal
6
+ def apply(number, options = {})
7
+ super.gsub('¤', options[:percent_sign] || '%')
8
+ end
9
+ end
10
+ end
11
+ end