twitter_cldr_js 1.0.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.
- data/Gemfile +12 -0
- data/History.txt +3 -0
- data/LICENSE +177 -0
- data/NOTICE +120 -0
- data/README.md +161 -0
- data/Rakefile +66 -0
- data/lib/assets/javascripts/twitter_cldr/twitter_cldr_af.js +887 -0
- data/lib/assets/javascripts/twitter_cldr/twitter_cldr_ar.js +887 -0
- data/lib/assets/javascripts/twitter_cldr/twitter_cldr_ca.js +887 -0
- data/lib/assets/javascripts/twitter_cldr/twitter_cldr_cs.js +887 -0
- data/lib/assets/javascripts/twitter_cldr/twitter_cldr_da.js +887 -0
- data/lib/assets/javascripts/twitter_cldr/twitter_cldr_de.js +887 -0
- data/lib/assets/javascripts/twitter_cldr/twitter_cldr_el.js +887 -0
- data/lib/assets/javascripts/twitter_cldr/twitter_cldr_en.js +887 -0
- data/lib/assets/javascripts/twitter_cldr/twitter_cldr_es.js +887 -0
- data/lib/assets/javascripts/twitter_cldr/twitter_cldr_eu.js +887 -0
- data/lib/assets/javascripts/twitter_cldr/twitter_cldr_fa.js +887 -0
- data/lib/assets/javascripts/twitter_cldr/twitter_cldr_fi.js +887 -0
- data/lib/assets/javascripts/twitter_cldr/twitter_cldr_fil.js +887 -0
- data/lib/assets/javascripts/twitter_cldr/twitter_cldr_fr.js +887 -0
- data/lib/assets/javascripts/twitter_cldr/twitter_cldr_he.js +887 -0
- data/lib/assets/javascripts/twitter_cldr/twitter_cldr_hi.js +887 -0
- data/lib/assets/javascripts/twitter_cldr/twitter_cldr_hu.js +887 -0
- data/lib/assets/javascripts/twitter_cldr/twitter_cldr_id.js +887 -0
- data/lib/assets/javascripts/twitter_cldr/twitter_cldr_it.js +887 -0
- data/lib/assets/javascripts/twitter_cldr/twitter_cldr_ja.js +887 -0
- data/lib/assets/javascripts/twitter_cldr/twitter_cldr_ko.js +887 -0
- data/lib/assets/javascripts/twitter_cldr/twitter_cldr_msa.js +887 -0
- data/lib/assets/javascripts/twitter_cldr/twitter_cldr_nl.js +887 -0
- data/lib/assets/javascripts/twitter_cldr/twitter_cldr_no.js +887 -0
- data/lib/assets/javascripts/twitter_cldr/twitter_cldr_pl.js +887 -0
- data/lib/assets/javascripts/twitter_cldr/twitter_cldr_pt.js +887 -0
- data/lib/assets/javascripts/twitter_cldr/twitter_cldr_ru.js +887 -0
- data/lib/assets/javascripts/twitter_cldr/twitter_cldr_sv.js +887 -0
- data/lib/assets/javascripts/twitter_cldr/twitter_cldr_th.js +887 -0
- data/lib/assets/javascripts/twitter_cldr/twitter_cldr_tr.js +887 -0
- data/lib/assets/javascripts/twitter_cldr/twitter_cldr_uk.js +887 -0
- data/lib/assets/javascripts/twitter_cldr/twitter_cldr_ur.js +887 -0
- data/lib/assets/javascripts/twitter_cldr/twitter_cldr_zh-cn.js +887 -0
- data/lib/assets/javascripts/twitter_cldr/twitter_cldr_zh-tw.js +887 -0
- data/lib/twitter_cldr/js/compiler.rb +49 -0
- data/lib/twitter_cldr/js/mustache/bundle.coffee +15 -0
- data/lib/twitter_cldr/js/mustache/calendars/datetime.coffee +240 -0
- data/lib/twitter_cldr/js/mustache/calendars/timespan.coffee +52 -0
- data/lib/twitter_cldr/js/mustache/numbers/numbers.coffee +179 -0
- data/lib/twitter_cldr/js/mustache/plurals/rules.coffee +14 -0
- data/lib/twitter_cldr/js/mustache/shared/currencies.coffee +27 -0
- data/lib/twitter_cldr/js/renderers/base.rb +18 -0
- data/lib/twitter_cldr/js/renderers/bundle.rb +18 -0
- data/lib/twitter_cldr/js/renderers/calendars/datetime_renderer.rb +34 -0
- data/lib/twitter_cldr/js/renderers/calendars/timespan_renderer.rb +39 -0
- data/lib/twitter_cldr/js/renderers/numbers/numbers_renderer.rb +31 -0
- data/lib/twitter_cldr/js/renderers/plurals/rules/plural_rules_compiler.rb +93 -0
- data/lib/twitter_cldr/js/renderers/plurals/rules/plural_rules_renderer.rb +26 -0
- data/lib/twitter_cldr/js/renderers/shared/currencies_renderer.rb +23 -0
- data/lib/twitter_cldr/js/renderers.rb +31 -0
- data/lib/twitter_cldr/js/tasks/tasks.rake +14 -0
- data/lib/twitter_cldr/js/tasks/tasks.rb +88 -0
- data/lib/twitter_cldr/js/version.rb +10 -0
- data/lib/twitter_cldr/js.rb +31 -0
- data/spec/js/calendars/datetime.spec.js +418 -0
- data/spec/js/calendars/timespan.spec.js +91 -0
- data/spec/js/numbers/currency.spec.js +34 -0
- data/spec/js/numbers/decimal.spec.js +24 -0
- data/spec/js/numbers/helpers/fraction.spec.js +23 -0
- data/spec/js/numbers/helpers/integer.spec.js +100 -0
- data/spec/js/numbers/number.spec.js +70 -0
- data/spec/js/numbers/percent.spec.js +22 -0
- data/spec/js/plurals/plural_rules.spec.js +28 -0
- data/spec/ruby/renderers/plurals/plural_rules_compiler_spec.rb +56 -0
- data/spec/ruby/spec_helper.rb +11 -0
- data/twitter_cldr_js.gemspec +34 -0
- metadata +224 -0
@@ -0,0 +1,49 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
# Copyright 2012 Twitter, Inc
|
4
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
5
|
+
|
6
|
+
module TwitterCldr
|
7
|
+
module Js
|
8
|
+
class Compiler
|
9
|
+
attr_reader :locales
|
10
|
+
|
11
|
+
def initialize(options = {})
|
12
|
+
@locales = options[:locales] || TwitterCldr.supported_locales
|
13
|
+
@features = options[:features] || renderers.keys
|
14
|
+
end
|
15
|
+
|
16
|
+
def compile_each(options = {})
|
17
|
+
options[:minify] = true unless options.include?(:minify)
|
18
|
+
|
19
|
+
@locales.each do |locale|
|
20
|
+
contents = ""
|
21
|
+
|
22
|
+
@features.each do |feature|
|
23
|
+
renderer_const = renderers[feature]
|
24
|
+
contents << renderer_const.new(:locale => locale).render if renderer_const
|
25
|
+
end
|
26
|
+
|
27
|
+
bundle = TwitterCldr::Js::Renderers::Bundle.new
|
28
|
+
bundle[:contents] = contents
|
29
|
+
result = CoffeeScript.compile(bundle.render, :bare => true)
|
30
|
+
result = Uglifier.compile(result) if options[:minify]
|
31
|
+
|
32
|
+
yield result, TwitterCldr.twitter_locale(locale)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
def renderers
|
39
|
+
@renderers ||= {
|
40
|
+
:plural_rules => TwitterCldr::Js::Renderers::PluralRules::PluralRulesRenderer,
|
41
|
+
:timespan => TwitterCldr::Js::Renderers::Calendars::TimespanRenderer,
|
42
|
+
:datetime => TwitterCldr::Js::Renderers::Calendars::DateTimeRenderer,
|
43
|
+
:numbers => TwitterCldr::Js::Renderers::Numbers::NumbersRenderer,
|
44
|
+
:currencies => TwitterCldr::Js::Renderers::Shared::CurrenciesRenderer
|
45
|
+
}
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
###
|
2
|
+
// Copyright 2012 Twitter, Inc
|
3
|
+
// http://www.apache.org/licenses/LICENSE-2.0
|
4
|
+
|
5
|
+
// TwitterCLDR (JavaScript) v{{version}}
|
6
|
+
// Authors: Cameron Dutro [@camertron]
|
7
|
+
Kirill Lashuk [@KL_7]
|
8
|
+
portions by Sven Fuchs [@svenfuchs]
|
9
|
+
// Homepage: https://twitter.com
|
10
|
+
// Description: Provides date, time, number, and list formatting functionality for various Twitter-supported locales in Javascript.
|
11
|
+
###
|
12
|
+
|
13
|
+
TwitterCldr = {}
|
14
|
+
{{{contents}}}
|
15
|
+
exports[key] = obj for key, obj of TwitterCldr if exports? # for node
|
@@ -0,0 +1,240 @@
|
|
1
|
+
# Copyright 2012 Twitter, Inc
|
2
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
3
|
+
|
4
|
+
TwitterCldr.DateTimeFormatter = class DateTimeFormatter
|
5
|
+
constructor: ->
|
6
|
+
@tokens = `{{{tokens}}}`
|
7
|
+
@calendar = `{{{calendar}}}`
|
8
|
+
@weekday_keys = ["sun", "mon", "tue", "wed", "thu", "fri", "sat"]
|
9
|
+
@methods = # ignoring u, l, g, j, A
|
10
|
+
'G': 'era'
|
11
|
+
'y': 'year'
|
12
|
+
'Y': 'year_of_week_of_year'
|
13
|
+
'Q': 'quarter'
|
14
|
+
'q': 'quarter_stand_alone'
|
15
|
+
'M': 'month'
|
16
|
+
'L': 'month_stand_alone'
|
17
|
+
'w': 'week_of_year'
|
18
|
+
'W': 'week_of_month'
|
19
|
+
'd': 'day'
|
20
|
+
'D': 'day_of_month'
|
21
|
+
'F': 'day_of_week_in_month'
|
22
|
+
'E': 'weekday'
|
23
|
+
'e': 'weekday_local'
|
24
|
+
'c': 'weekday_local_stand_alone'
|
25
|
+
'a': 'period'
|
26
|
+
'h': 'hour'
|
27
|
+
'H': 'hour'
|
28
|
+
'K': 'hour'
|
29
|
+
'k': 'hour'
|
30
|
+
'm': 'minute'
|
31
|
+
's': 'second'
|
32
|
+
'S': 'second_fraction'
|
33
|
+
'z': 'timezone'
|
34
|
+
'Z': 'timezone'
|
35
|
+
'v': 'timezone_generic_non_location'
|
36
|
+
'V': 'timezone_metazone'
|
37
|
+
|
38
|
+
format: (obj, options) ->
|
39
|
+
format_token = (token) =>
|
40
|
+
result = ""
|
41
|
+
|
42
|
+
switch token.type
|
43
|
+
when "pattern"
|
44
|
+
return this.result_for_token(token, obj)
|
45
|
+
else
|
46
|
+
if token.value.length > 0 && token.value[0] == "'" && token.value[token.value.length - 1] == "'"
|
47
|
+
return token.value.substring(1, token.value.length - 1)
|
48
|
+
else
|
49
|
+
return token.value
|
50
|
+
|
51
|
+
tokens = this.get_tokens(obj, options)
|
52
|
+
return (format_token(token) for token in tokens).join("")
|
53
|
+
|
54
|
+
get_tokens: (obj, options) ->
|
55
|
+
return @tokens[options.format || "date_time"][options.type || "default"]
|
56
|
+
|
57
|
+
result_for_token: (token, date) ->
|
58
|
+
return this[@methods[token.value[0]]](date, token.value, token.value.length)
|
59
|
+
|
60
|
+
era: (date, pattern, length) ->
|
61
|
+
switch length
|
62
|
+
when 1, 2, 3
|
63
|
+
choices = @calendar["eras"]["abbr"]
|
64
|
+
else
|
65
|
+
choices = @calendar["eras"]["name"]
|
66
|
+
|
67
|
+
index = if (date.getFullYear() < 0) then 0 else 1
|
68
|
+
return choices[index]
|
69
|
+
|
70
|
+
year: (date, pattern, length) ->
|
71
|
+
year = date.getFullYear().toString()
|
72
|
+
|
73
|
+
if length == 2
|
74
|
+
if year.length != 1
|
75
|
+
year = year.slice(-2)
|
76
|
+
|
77
|
+
if length > 1
|
78
|
+
year = ("0000" + year).slice(-length)
|
79
|
+
|
80
|
+
return year
|
81
|
+
|
82
|
+
year_of_week_of_year: (date, pattern, length) ->
|
83
|
+
throw 'not implemented'
|
84
|
+
|
85
|
+
day_of_week_in_month: (date, pattern, length) -> # e.g. 2nd Wed in July
|
86
|
+
throw 'not implemented'
|
87
|
+
|
88
|
+
quarter: (date, pattern, length) ->
|
89
|
+
# the bitwise OR is used here to truncate the decimal produced by the / 3
|
90
|
+
quarter = ((date.getMonth() / 3) | 0) + 1
|
91
|
+
|
92
|
+
switch length
|
93
|
+
when 1
|
94
|
+
return quarter.toString()
|
95
|
+
when 2
|
96
|
+
return ("0000" + quarter.toString()).slice(-length)
|
97
|
+
when 3
|
98
|
+
return @calendar.quarters.format.abbreviated[quarter]
|
99
|
+
when 4
|
100
|
+
return @calendar.quarters.format.wide[quarter]
|
101
|
+
|
102
|
+
quarter_stand_alone: (date, pattern, length) ->
|
103
|
+
quarter = (date.getMonth() - 1) / 3 + 1
|
104
|
+
|
105
|
+
switch length
|
106
|
+
when 1
|
107
|
+
return quarter.toString()
|
108
|
+
when 2
|
109
|
+
return ("0000" + quarter.toString()).slice(-length)
|
110
|
+
when 3
|
111
|
+
throw 'not yet implemented (requires cldr\'s "multiple inheritance")'
|
112
|
+
when 4
|
113
|
+
throw 'not yet implemented (requires cldr\'s "multiple inheritance")'
|
114
|
+
when 5
|
115
|
+
return @calendar.quarters['stand-alone'].narrow[quarter]
|
116
|
+
|
117
|
+
month: (date, pattern, length) ->
|
118
|
+
month_str = (date.getMonth() + 1).toString()
|
119
|
+
|
120
|
+
switch length
|
121
|
+
when 1
|
122
|
+
return month_str
|
123
|
+
when 2
|
124
|
+
return ("0000" + month_str).slice(-length)
|
125
|
+
when 3
|
126
|
+
return @calendar.months.format.abbreviated[month_str]
|
127
|
+
when 4
|
128
|
+
return @calendar.months.format.wide[month_str]
|
129
|
+
when 5
|
130
|
+
throw 'not yet implemented (requires cldr\'s "multiple inheritance")'
|
131
|
+
else
|
132
|
+
throw "Unknown date format"
|
133
|
+
|
134
|
+
month_stand_alone: (date, pattern, length) ->
|
135
|
+
switch length
|
136
|
+
when 1
|
137
|
+
return date.getMonth().toString()
|
138
|
+
when 2
|
139
|
+
return ("0000" + date.getMonth().toString()).slice(-length)
|
140
|
+
when 3
|
141
|
+
throw 'not yet implemented (requires cldr\'s "multiple inheritance")'
|
142
|
+
when 4
|
143
|
+
throw 'not yet implemented (requires cldr\'s "multiple inheritance")'
|
144
|
+
when 5
|
145
|
+
return @calendar.months['stand-alone'].narrow[date.month]
|
146
|
+
else
|
147
|
+
throw "Unknown date format"
|
148
|
+
|
149
|
+
day: (date, pattern, length) ->
|
150
|
+
switch length
|
151
|
+
when 1
|
152
|
+
return date.getDate().toString()
|
153
|
+
when 2
|
154
|
+
return ("0000" + date.getDate().toString()).slice(-length)
|
155
|
+
|
156
|
+
weekday: (date, pattern, length) ->
|
157
|
+
key = @weekday_keys[date.getDay()]
|
158
|
+
|
159
|
+
switch length
|
160
|
+
when 1, 2, 3
|
161
|
+
return @calendar.days.format.abbreviated[key]
|
162
|
+
when 4
|
163
|
+
return @calendar.days.format.wide[key]
|
164
|
+
when 5
|
165
|
+
return @calendar.days['stand-alone'].narrow[key]
|
166
|
+
|
167
|
+
weekday_local: (date, pattern, length) ->
|
168
|
+
# "Like E except adds a numeric value depending on the local starting day of the week"
|
169
|
+
# CLDR does not contain data as to which day is the first day of the week, so we will assume Monday (Ruby default)
|
170
|
+
switch length
|
171
|
+
when 1, 2
|
172
|
+
day = date.getDay()
|
173
|
+
return (if day == 0 then "7" else day.toString())
|
174
|
+
else
|
175
|
+
return this.weekday(date, pattern, length)
|
176
|
+
|
177
|
+
weekday_local_stand_alone: (date, pattern, length) ->
|
178
|
+
switch length
|
179
|
+
when 1
|
180
|
+
return this.weekday_local(date, pattern, length)
|
181
|
+
else
|
182
|
+
return this.weekday(date, pattern, length)
|
183
|
+
|
184
|
+
period: (time, pattern, length) ->
|
185
|
+
if time.getHours() > 11
|
186
|
+
return @calendar.periods.format.wide["pm"]
|
187
|
+
else
|
188
|
+
return @calendar.periods.format.wide["am"]
|
189
|
+
|
190
|
+
hour: (time, pattern, length) ->
|
191
|
+
hour = time.getHours()
|
192
|
+
|
193
|
+
switch pattern[0]
|
194
|
+
when 'h'
|
195
|
+
if hour > 12
|
196
|
+
hour = hour - 12
|
197
|
+
else if hour == 0
|
198
|
+
hour = 12
|
199
|
+
when 'K'
|
200
|
+
if hour > 11
|
201
|
+
hour = hour - 12
|
202
|
+
when 'k'
|
203
|
+
if hour == 0
|
204
|
+
hour = 24
|
205
|
+
|
206
|
+
if length == 1
|
207
|
+
return hour.toString()
|
208
|
+
else
|
209
|
+
return ("000000" + hour.toString()).slice(-length)
|
210
|
+
|
211
|
+
minute: (time, pattern, length) ->
|
212
|
+
if length == 1
|
213
|
+
return time.getMinutes().toString()
|
214
|
+
else
|
215
|
+
return ("000000" + time.getMinutes().toString()).slice(-length)
|
216
|
+
|
217
|
+
second: (time, pattern, length) ->
|
218
|
+
if length == 1
|
219
|
+
return time.getSeconds().toString()
|
220
|
+
else
|
221
|
+
return ("000000" + time.getSeconds().toString()).slice(-length)
|
222
|
+
|
223
|
+
second_fraction: (time, pattern, length) ->
|
224
|
+
if length > 6
|
225
|
+
throw 'can not use the S format with more than 6 digits'
|
226
|
+
|
227
|
+
return ("000000" + Math.round(Math.pow(time.getMilliseconds() * 100.0, 6 - length)).toString()).slice(-length)
|
228
|
+
|
229
|
+
timezone: (time, pattern, length) ->
|
230
|
+
hours = ("00" + (time.getTimezoneOffset() / 60).toString()).slice(-2)
|
231
|
+
minutes = ("00" + (time.getTimezoneOffset() % 60).toString()).slice(-2)
|
232
|
+
|
233
|
+
switch length
|
234
|
+
when 1, 2, 3
|
235
|
+
return "-" + hours + ":" + minutes
|
236
|
+
else
|
237
|
+
return "UTC -" + hours + ":" + minutes
|
238
|
+
|
239
|
+
timezone_generic_non_location: (time, pattern, length) ->
|
240
|
+
throw 'not yet implemented (requires timezone translation data")'
|
@@ -0,0 +1,52 @@
|
|
1
|
+
# Copyright 2012 Twitter, Inc
|
2
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
3
|
+
|
4
|
+
TwitterCldr.TimespanFormatter = class TimespanFormatter
|
5
|
+
constructor: ->
|
6
|
+
@default_type = "default"
|
7
|
+
@tokens = `{{{tokens}}}`
|
8
|
+
@time_in_seconds = {
|
9
|
+
"second": 1,
|
10
|
+
"minute": 60,
|
11
|
+
"hour": 3600,
|
12
|
+
"day": 86400,
|
13
|
+
"week": 604800,
|
14
|
+
"month": 2629743.83,
|
15
|
+
"year": 31556926
|
16
|
+
}
|
17
|
+
|
18
|
+
format: (seconds, options = {}) ->
|
19
|
+
options["direction"] ||= (if seconds < 0 then "ago" else "until")
|
20
|
+
options["unit"] = this.calculate_unit(Math.abs(seconds)) if options["unit"] is null or options["unit"] is undefined
|
21
|
+
options["type"] ||= @default_type
|
22
|
+
options["number"] = this.calculate_time(Math.abs(seconds), options["unit"])
|
23
|
+
number = this.calculate_time(Math.abs(seconds), options["unit"])
|
24
|
+
options["rule"] = TwitterCldr.PluralRules.rule_for(number)
|
25
|
+
|
26
|
+
strings = (token.value for token in @tokens[options["direction"]][options["unit"]][options["type"]][options["rule"]])
|
27
|
+
strings.join("").replace(/\{[0-9]\}/, number.toString())
|
28
|
+
|
29
|
+
calculate_unit: (seconds) ->
|
30
|
+
if seconds < 30
|
31
|
+
"second"
|
32
|
+
else if seconds < 2670
|
33
|
+
"minute"
|
34
|
+
else if seconds < 86369
|
35
|
+
"hour"
|
36
|
+
else if seconds < 604800
|
37
|
+
"day"
|
38
|
+
else if seconds < 2591969
|
39
|
+
"week"
|
40
|
+
else if seconds < 31556926
|
41
|
+
"month"
|
42
|
+
else
|
43
|
+
"year"
|
44
|
+
|
45
|
+
# 0 <-> 29 secs # => seconds
|
46
|
+
# 30 secs <-> 44 mins, 29 secs # => minutes
|
47
|
+
# 44 mins, 30 secs <-> 23 hrs, 59 mins, 29 secs # => hours
|
48
|
+
# 23 hrs, 59 mins, 29 secs <-> 29 days, 23 hrs, 59 mins, 29 secs # => days
|
49
|
+
# 29 days, 23 hrs, 59 mins, 29 secs <-> 1 yr minus 1 sec # => months
|
50
|
+
# 1 yr <-> max time or date # => years
|
51
|
+
calculate_time: (seconds, unit) ->
|
52
|
+
Math.round(seconds / @time_in_seconds[unit])
|
@@ -0,0 +1,179 @@
|
|
1
|
+
# Copyright 2012 Twitter, Inc
|
2
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
3
|
+
|
4
|
+
TwitterCldr.NumberFormatter = class NumberFormatter
|
5
|
+
constructor: ->
|
6
|
+
@all_tokens = `{{{tokens}}}`
|
7
|
+
@tokens = []
|
8
|
+
@symbols = `{{{symbols}}}`
|
9
|
+
|
10
|
+
@default_symbols =
|
11
|
+
'group': ','
|
12
|
+
'decimal': '.'
|
13
|
+
'plus_sign': '+'
|
14
|
+
'minus_sign': '-'
|
15
|
+
|
16
|
+
format: (number, options = {}) ->
|
17
|
+
opts = this.default_format_options_for(number)
|
18
|
+
|
19
|
+
for key, val of options
|
20
|
+
opts[key] = if options[key]? then options[key] else opts[key]
|
21
|
+
|
22
|
+
[prefix, suffix, integer_format, fraction_format] = this.partition_tokens(this.get_tokens(number, opts))
|
23
|
+
[int, fraction] = this.parse_number(number, opts)
|
24
|
+
result = integer_format.apply(parseFloat(int), opts)
|
25
|
+
result += fraction_format.apply(fraction, opts) if fraction
|
26
|
+
sign = if number < 0 && prefix != "-" then @symbols.minus_sign || @default_symbols.minus_sign else ""
|
27
|
+
"#{prefix}#{result}#{suffix}"
|
28
|
+
|
29
|
+
partition_tokens: (tokens) ->
|
30
|
+
[tokens[0] || "",
|
31
|
+
tokens[2] || "",
|
32
|
+
new IntegerHelper(tokens[1], @symbols),
|
33
|
+
new FractionHelper(tokens[1], @symbols)]
|
34
|
+
|
35
|
+
parse_number: (number, options = {}) ->
|
36
|
+
if options.precision?
|
37
|
+
precision = options.precision
|
38
|
+
else
|
39
|
+
precision = this.precision_from(number)
|
40
|
+
|
41
|
+
number = this.round_to(number, precision)
|
42
|
+
Math.abs(number).toFixed(precision).split(".")
|
43
|
+
|
44
|
+
precision_from: (num) ->
|
45
|
+
parts = num.toString().split(".")
|
46
|
+
if parts.length == 2 then parts[1].length else 0
|
47
|
+
|
48
|
+
round_to: (number, precision) ->
|
49
|
+
factor = Math.pow(10, precision)
|
50
|
+
Math.round(number * factor) / factor
|
51
|
+
|
52
|
+
get_tokens: ->
|
53
|
+
throw "get_tokens() not implemented - use a derived class like PercentFormatter."
|
54
|
+
|
55
|
+
TwitterCldr.PercentFormatter = class PercentFormatter extends NumberFormatter
|
56
|
+
constructor: (options = {}) ->
|
57
|
+
@default_percent_sign = "%"
|
58
|
+
super
|
59
|
+
|
60
|
+
format: (number, options = {}) ->
|
61
|
+
super(number, options).replace('¤', @symbols.percent_sign || @default_percent_sign)
|
62
|
+
|
63
|
+
default_format_options_for: (number) ->
|
64
|
+
precision: 0
|
65
|
+
|
66
|
+
get_tokens: (number, options) ->
|
67
|
+
if number < 0 then @all_tokens.percent.negative else @all_tokens.percent.positive
|
68
|
+
|
69
|
+
TwitterCldr.DecimalFormatter = class DecimalFormatter extends NumberFormatter
|
70
|
+
format: (number, options = {}) ->
|
71
|
+
try
|
72
|
+
super(number, options)
|
73
|
+
catch error
|
74
|
+
number
|
75
|
+
|
76
|
+
default_format_options_for: (number) ->
|
77
|
+
precision: this.precision_from(number)
|
78
|
+
|
79
|
+
get_tokens: (number, options = {}) ->
|
80
|
+
if number < 0 then @all_tokens.decimal.negative else @all_tokens.decimal.positive
|
81
|
+
|
82
|
+
TwitterCldr.CurrencyFormatter = class CurrencyFormatter extends NumberFormatter
|
83
|
+
constructor: (options = {}) ->
|
84
|
+
@default_currency_symbol = "$"
|
85
|
+
@default_precision = 2
|
86
|
+
super
|
87
|
+
|
88
|
+
format: (number, options = {}) ->
|
89
|
+
if options.currency
|
90
|
+
if TwitterCldr.Currencies?
|
91
|
+
currency = TwitterCldr.Currencies.for_code(options.currency)
|
92
|
+
currency ||= TwitterCldr.Currencies.for_country(options.currency)
|
93
|
+
currency ||= symbol: options.currency
|
94
|
+
else
|
95
|
+
currency = symbol: options.currency
|
96
|
+
else
|
97
|
+
currency = symbol: @default_currency_symbol
|
98
|
+
|
99
|
+
super(number, options).replace('¤', currency.symbol)
|
100
|
+
|
101
|
+
default_format_options_for: (number) ->
|
102
|
+
precision = this.precision_from(number)
|
103
|
+
if precision == 0 then precision = @default_precision
|
104
|
+
precision: precision
|
105
|
+
|
106
|
+
get_tokens: (number, options = {}) ->
|
107
|
+
if number < 0 then @all_tokens.currency.negative else @all_tokens.currency.positive
|
108
|
+
|
109
|
+
TwitterCldr.NumberFormatter.BaseHelper = class BaseHelper
|
110
|
+
interpolate: (string, value, orientation = "right") ->
|
111
|
+
value = value.toString()
|
112
|
+
length = value.length
|
113
|
+
start = if orientation == "left" then 0 else -length
|
114
|
+
string = (("#" for i in [0...length]).join("") + string).slice(-length) if string.length < length
|
115
|
+
|
116
|
+
if start < 0
|
117
|
+
string = string[0...(start + string.length)] + value
|
118
|
+
else
|
119
|
+
string = string[0...start] + value + string[(length)..-1]
|
120
|
+
|
121
|
+
string.replace(/#/g, "")
|
122
|
+
|
123
|
+
TwitterCldr.NumberFormatter.IntegerHelper = class IntegerHelper extends BaseHelper
|
124
|
+
constructor: (token, symbols = {}) ->
|
125
|
+
format = token.split('.')[0]
|
126
|
+
@format = this.prepare_format(format, symbols)
|
127
|
+
@groups = this.parse_groups(format)
|
128
|
+
@separator = symbols.group || ','
|
129
|
+
|
130
|
+
apply: (number, options = {}) ->
|
131
|
+
this.format_groups(this.interpolate(@format, parseInt(number)))
|
132
|
+
|
133
|
+
format_groups: (string) ->
|
134
|
+
return string if @groups.length == 0
|
135
|
+
tokens = []
|
136
|
+
|
137
|
+
cur_token = this.chop_group(string, @groups[0])
|
138
|
+
tokens.push(cur_token)
|
139
|
+
string = string[0...(string.length - cur_token.length)] if cur_token
|
140
|
+
|
141
|
+
while string.length > @groups[@groups.length - 1]
|
142
|
+
cur_token = this.chop_group(string, @groups[@groups.length - 1])
|
143
|
+
tokens.push(cur_token)
|
144
|
+
string = string[0...(string.length - cur_token.length)] if cur_token
|
145
|
+
|
146
|
+
tokens.push(string)
|
147
|
+
(token for token in tokens when token != null).reverse().join(@separator)
|
148
|
+
|
149
|
+
parse_groups: (format) ->
|
150
|
+
return [] unless index = format.lastIndexOf(',')
|
151
|
+
rest = format[0...index]
|
152
|
+
widths = [format.length - index - 1]
|
153
|
+
widths.push(rest.length - rest.lastIndexOf(',') - 1) if rest.lastIndexOf(',') > -1
|
154
|
+
widths = (width for width in widths when width != null) # compact
|
155
|
+
widths.reverse() # uniq
|
156
|
+
(widths[index] for index in [0...widths.length] when widths.indexOf(widths[index], index + 1) == -1).reverse()
|
157
|
+
|
158
|
+
chop_group: (string, size) ->
|
159
|
+
if string.length > size then string[-size..-1] else null
|
160
|
+
|
161
|
+
prepare_format: (format, symbols) ->
|
162
|
+
format.replace(",", "").replace("+", symbols.plus_sign).replace("-", symbols.minus_sign)
|
163
|
+
|
164
|
+
TwitterCldr.NumberFormatter.FractionHelper = class FractionHelper extends BaseHelper
|
165
|
+
constructor: (token, symbols = {}) ->
|
166
|
+
@format = if token then token.split('.').pop() else ""
|
167
|
+
@decimal = symbols.decimal || "."
|
168
|
+
@precision = @format.length
|
169
|
+
|
170
|
+
apply: (fraction, options = {}) ->
|
171
|
+
precision = if options.precision? then options.precision else @precision
|
172
|
+
if precision > 0
|
173
|
+
@decimal + this.interpolate(this.format_for(options), fraction, "left")
|
174
|
+
else
|
175
|
+
""
|
176
|
+
|
177
|
+
format_for: (options) ->
|
178
|
+
precision = if options.precision? then options.precision else @precision
|
179
|
+
if precision then ("0" for i in [0...precision]).join("") else @format
|
@@ -0,0 +1,14 @@
|
|
1
|
+
# Copyright 2012 Twitter, Inc
|
2
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
3
|
+
|
4
|
+
TwitterCldr.PluralRules = class PluralRules
|
5
|
+
@rules = `{{{rules}}}`
|
6
|
+
|
7
|
+
@all: ->
|
8
|
+
return @rules.keys
|
9
|
+
|
10
|
+
@rule_for: (number) ->
|
11
|
+
try
|
12
|
+
return @rules.rule(number)
|
13
|
+
catch error
|
14
|
+
return "other"
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# Copyright 2012 Twitter, Inc
|
2
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
3
|
+
|
4
|
+
TwitterCldr.Currencies = class Currencies
|
5
|
+
@currencies = `{{{currencies}}}`
|
6
|
+
|
7
|
+
@countries: ->
|
8
|
+
@names ||= (country_name for country_name, data of @currencies)
|
9
|
+
|
10
|
+
@currency_codes: ->
|
11
|
+
@codes ||= (data.code for country_name, data of @currencies)
|
12
|
+
|
13
|
+
@for_country: (country_name) ->
|
14
|
+
@currencies[country_name]
|
15
|
+
|
16
|
+
@for_code: (currency_code) ->
|
17
|
+
final = null
|
18
|
+
for country_name, data of @currencies
|
19
|
+
if data.code == currency_code
|
20
|
+
final =
|
21
|
+
country: country_name
|
22
|
+
code: data.code
|
23
|
+
symbol: data.symbol
|
24
|
+
currency: data.currency
|
25
|
+
break
|
26
|
+
|
27
|
+
final
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
# Copyright 2012 Twitter, Inc
|
4
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
5
|
+
|
6
|
+
module TwitterCldr
|
7
|
+
module Js
|
8
|
+
module Renderers
|
9
|
+
class Base < Mustache
|
10
|
+
|
11
|
+
def initialize(options = {})
|
12
|
+
@locale = options[:locale]
|
13
|
+
end
|
14
|
+
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
# Copyright 2012 Twitter, Inc
|
4
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
5
|
+
|
6
|
+
module TwitterCldr
|
7
|
+
module Js
|
8
|
+
module Renderers
|
9
|
+
class Bundle < Mustache
|
10
|
+
self.template_file = File.expand_path(File.join(File.dirname(__FILE__), "..", "mustache/bundle.coffee"))
|
11
|
+
|
12
|
+
def version
|
13
|
+
TwitterCldr::VERSION
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
# Copyright 2012 Twitter, Inc
|
4
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
5
|
+
|
6
|
+
module TwitterCldr
|
7
|
+
module Js
|
8
|
+
module Renderers
|
9
|
+
module Calendars
|
10
|
+
class DateTimeRenderer < TwitterCldr::Js::Renderers::Base
|
11
|
+
self.template_file = File.expand_path(File.join(File.dirname(__FILE__), "../..", "mustache/calendars/datetime.coffee"))
|
12
|
+
|
13
|
+
def tokens
|
14
|
+
tokens = {}
|
15
|
+
{ :date_time => TwitterCldr::Tokenizers::DateTimeTokenizer,
|
16
|
+
:time => TwitterCldr::Tokenizers::TimeTokenizer,
|
17
|
+
:date => TwitterCldr::Tokenizers::DateTokenizer }.each_pair do |name, const|
|
18
|
+
tokenizer = const.new(:locale => @locale)
|
19
|
+
tokens[name] = const::VALID_TYPES.inject({}) do |ret, type|
|
20
|
+
ret[type] = tokenizer.tokens(:type => type).map(&:to_hash)
|
21
|
+
ret
|
22
|
+
end
|
23
|
+
end
|
24
|
+
tokens.to_json
|
25
|
+
end
|
26
|
+
|
27
|
+
def calendar
|
28
|
+
TwitterCldr::Tokenizers::DateTimeTokenizer.new(:locale => @locale).calendar.to_json
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
# Copyright 2012 Twitter, Inc
|
4
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
5
|
+
|
6
|
+
include TwitterCldr::Tokenizers
|
7
|
+
include TwitterCldr::Formatters
|
8
|
+
|
9
|
+
module TwitterCldr
|
10
|
+
module Js
|
11
|
+
module Renderers
|
12
|
+
module Calendars
|
13
|
+
|
14
|
+
class TimespanRenderer < TwitterCldr::Js::Renderers::Base
|
15
|
+
self.template_file = File.expand_path(File.join(File.dirname(__FILE__), "../..", "mustache/calendars/timespan.coffee"))
|
16
|
+
|
17
|
+
def tokens
|
18
|
+
tokenizer = TimespanTokenizer.new(:locale => @locale)
|
19
|
+
[:ago, :until, :none].inject({}) do |final, direction|
|
20
|
+
final[direction] = TimespanTokenizer::VALID_UNITS.inject({}) do |unit_hash, unit|
|
21
|
+
unit_hash[unit] = tokenizer.all_types_for(unit, direction).inject({}) do |type_hash, type|
|
22
|
+
type_hash[type] = Plurals::Rules.all_for(@locale).inject({}) do |rule_hash, rule|
|
23
|
+
rule_hash[rule] = tokenizer.tokens(:direction => direction, :unit => unit, :rule => rule, :type => type)
|
24
|
+
rule_hash
|
25
|
+
end
|
26
|
+
type_hash
|
27
|
+
end
|
28
|
+
unit_hash
|
29
|
+
end
|
30
|
+
final
|
31
|
+
end.to_json
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|