i18n-js 2.1.2 → 3.0.0.rc1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (55) hide show
  1. data/Gemfile.lock +23 -31
  2. data/README.md +318 -0
  3. data/Rakefile +6 -6
  4. data/app/assets/javascripts/i18n/translations.js.erb +3 -0
  5. data/i18n-js.gemspec +5 -6
  6. data/lib/i18n-js.rb +1 -177
  7. data/lib/i18n.js +698 -0
  8. data/lib/i18n/js.rb +157 -0
  9. data/lib/i18n/js/engine.rb +22 -0
  10. data/lib/{i18n-js → i18n/js}/middleware.rb +7 -7
  11. data/lib/i18n/js/version.rb +10 -0
  12. data/spec/{resources → fixtures}/custom_path.yml +1 -1
  13. data/spec/{resources → fixtures}/default.yml +1 -1
  14. data/spec/fixtures/js_file_per_locale.yml +3 -0
  15. data/spec/{resources → fixtures}/locales.yml +1 -1
  16. data/spec/fixtures/multiple_conditions.yml +5 -0
  17. data/spec/{resources → fixtures}/multiple_files.yml +3 -3
  18. data/spec/{resources → fixtures}/no_config.yml +0 -0
  19. data/spec/{resources → fixtures}/no_scope.yml +1 -1
  20. data/spec/{resources → fixtures}/simple_scope.yml +1 -1
  21. data/spec/i18n_js_spec.rb +122 -0
  22. data/spec/js/currency.spec.js +60 -0
  23. data/spec/js/current_locale.spec.js +19 -0
  24. data/spec/js/dates.spec.js +218 -0
  25. data/spec/js/defaults.spec.js +23 -0
  26. data/spec/js/interpolation.spec.js +28 -0
  27. data/spec/js/jasmine/MIT.LICENSE +20 -0
  28. data/spec/js/jasmine/jasmine-html.js +190 -0
  29. data/spec/js/jasmine/jasmine.css +166 -0
  30. data/spec/js/jasmine/jasmine.js +2476 -0
  31. data/spec/js/jasmine/jasmine_favicon.png +0 -0
  32. data/spec/js/localization.spec.js +41 -0
  33. data/spec/js/numbers.spec.js +124 -0
  34. data/spec/js/placeholder.spec.js +24 -0
  35. data/spec/js/pluralization.spec.js +105 -0
  36. data/spec/js/prepare_options.spec.js +41 -0
  37. data/spec/js/specs.html +46 -0
  38. data/spec/js/translate.spec.js +115 -0
  39. data/spec/js/translations.js +115 -0
  40. data/spec/spec_helper.rb +36 -14
  41. metadata +115 -69
  42. data/.gitignore +0 -5
  43. data/.rspec +0 -1
  44. data/README.rdoc +0 -305
  45. data/config/i18n-js.yml +0 -22
  46. data/lib/i18n-js/engine.rb +0 -62
  47. data/lib/i18n-js/railtie.rb +0 -13
  48. data/lib/i18n-js/rake.rb +0 -16
  49. data/lib/i18n-js/version.rb +0 -10
  50. data/spec/i18n_spec.js +0 -768
  51. data/spec/i18n_spec.rb +0 -205
  52. data/spec/resources/js_file_per_locale.yml +0 -3
  53. data/spec/resources/multiple_conditions.yml +0 -6
  54. data/vendor/assets/javascripts/i18n.js +0 -450
  55. data/vendor/assets/javascripts/i18n/translations.js.erb +0 -7
@@ -1,205 +0,0 @@
1
- require "spec_helper"
2
-
3
- if File.basename(Rails.root) != "tmp"
4
- abort <<-TXT
5
- \e[31;5m
6
- WARNING: That will remove your project!
7
- Please go to #{File.expand_path(File.dirname(__FILE__) + "/..")} and run `rake spec`\e[0m
8
- TXT
9
- end
10
-
11
- describe SimplesIdeias::I18n do
12
- before do
13
- # Remove temporary directory if already present
14
- FileUtils.rm_r(Rails.root) if File.exist?(Rails.root)
15
-
16
- # Create temporary directory to test the files generation
17
- %w( config public/javascripts ).each do |path|
18
- FileUtils.mkdir_p Rails.root.join(path)
19
- end
20
-
21
- # Overwrite defaut locales path to use fixtures
22
- I18n.load_path = [File.dirname(__FILE__) + "/resources/locales.yml"]
23
- end
24
-
25
- after do
26
- # Remove temporary directory
27
- FileUtils.rm_r(Rails.root)
28
- end
29
-
30
- it "copies the configuration file" do
31
- File.should_not be_file(SimplesIdeias::I18n.config_file)
32
- SimplesIdeias::I18n.setup!
33
- File.should be_file(SimplesIdeias::I18n.config_file)
34
- end
35
-
36
- it "keeps existing configuration file" do
37
- File.open(SimplesIdeias::I18n.config_file, "w+") {|f| f << "ORIGINAL"}
38
- SimplesIdeias::I18n.setup!
39
-
40
- File.read(SimplesIdeias::I18n.config_file).should == "ORIGINAL"
41
- end
42
-
43
- it "copies JavaScript library" do
44
- path = Rails.root.join("public/javascripts/i18n.js")
45
-
46
- File.should_not be_file(path)
47
- SimplesIdeias::I18n.setup!
48
- File.should be_file(path)
49
- end
50
-
51
- it "loads configuration file" do
52
- set_config "default.yml"
53
- SimplesIdeias::I18n.setup!
54
-
55
- SimplesIdeias::I18n.config?.should be_true
56
- SimplesIdeias::I18n.config.should be_kind_of(HashWithIndifferentAccess)
57
- SimplesIdeias::I18n.config.should_not be_empty
58
- end
59
-
60
- it "sets empty hash as configuration when no file is found" do
61
- SimplesIdeias::I18n.config?.should be_false
62
- SimplesIdeias::I18n.config.should == {}
63
- end
64
-
65
- it "exports messages to default path when configuration file doesn't exist" do
66
- SimplesIdeias::I18n.export!
67
- Rails.root.join(SimplesIdeias::I18n.export_dir, "translations.js").should be_file
68
- end
69
-
70
- it "exports messages using custom output path" do
71
- set_config "custom_path.yml"
72
- SimplesIdeias::I18n.should_receive(:save).with(translations, "public/javascripts/translations/all.js")
73
- SimplesIdeias::I18n.export!
74
- end
75
-
76
- it "sets default scope to * when not specified" do
77
- set_config "no_scope.yml"
78
- SimplesIdeias::I18n.should_receive(:save).with(translations, "public/javascripts/no_scope.js")
79
- SimplesIdeias::I18n.export!
80
- end
81
-
82
- it "exports to multiple files" do
83
- set_config "multiple_files.yml"
84
- SimplesIdeias::I18n.export!
85
-
86
- File.should be_file(Rails.root.join("public/javascripts/all.js"))
87
- File.should be_file(Rails.root.join("public/javascripts/tudo.js"))
88
- end
89
-
90
- it "ignores an empty config file" do
91
- set_config "no_config.yml"
92
- SimplesIdeias::I18n.export!
93
- Rails.root.join(SimplesIdeias::I18n.export_dir, "translations.js").should be_file
94
- end
95
-
96
- it "exports to a JS file per available locale" do
97
- set_config "js_file_per_locale.yml"
98
- SimplesIdeias::I18n.export!
99
-
100
- File.should be_file(Rails.root.join("public/javascripts/i18n/en.js"))
101
- end
102
-
103
- it "exports with multiple conditions" do
104
- set_config "multiple_conditions.yml"
105
- SimplesIdeias::I18n.export!
106
- File.should be_file(Rails.root.join("public/javascripts/bitsnpieces.js"))
107
- end
108
-
109
- it "filters translations using scope *.date.formats" do
110
- result = SimplesIdeias::I18n.filter(translations, "*.date.formats")
111
- result[:en][:date].keys.should == [:formats]
112
- result[:fr][:date].keys.should == [:formats]
113
- end
114
-
115
- it "filters translations using scope [*.date.formats, *.number.currency.format]" do
116
- result = SimplesIdeias::I18n.scoped_translations(["*.date.formats", "*.number.currency.format"])
117
- result[:en].keys.collect(&:to_s).sort.should == %w[ date number ]
118
- result[:fr].keys.collect(&:to_s).sort.should == %w[ date number ]
119
- end
120
-
121
- it "filters translations using multi-star scope" do
122
- result = SimplesIdeias::I18n.scoped_translations("*.*.formats")
123
-
124
- result[:en].keys.collect(&:to_s).sort.should == %w[ date time ]
125
- result[:fr].keys.collect(&:to_s).sort.should == %w[ date time ]
126
-
127
- result[:en][:date].keys.should == [:formats]
128
- result[:en][:time].keys.should == [:formats]
129
-
130
- result[:fr][:date].keys.should == [:formats]
131
- result[:fr][:time].keys.should == [:formats]
132
- end
133
-
134
- it "filters translations using alternated stars" do
135
- result = SimplesIdeias::I18n.scoped_translations("*.admin.*.title")
136
-
137
- result[:en][:admin].keys.collect(&:to_s).sort.should == %w[ edit show ]
138
- result[:fr][:admin].keys.collect(&:to_s).sort.should == %w[ edit show ]
139
-
140
- result[:en][:admin][:show][:title].should == "Show"
141
- result[:fr][:admin][:show][:title].should == "Visualiser"
142
-
143
- result[:en][:admin][:edit][:title].should == "Edit"
144
- result[:fr][:admin][:edit][:title].should == "Editer"
145
- end
146
-
147
- it "performs a deep merge" do
148
- target = {:a => {:b => 1}}
149
- result = SimplesIdeias::I18n.deep_merge(target, {:a => {:c => 2}})
150
-
151
- result[:a].should == {:b => 1, :c => 2}
152
- end
153
-
154
- it "performs a banged deep merge" do
155
- target = {:a => {:b => 1}}
156
- SimplesIdeias::I18n.deep_merge!(target, {:a => {:c => 2}})
157
-
158
- target[:a].should == {:b => 1, :c => 2}
159
- end
160
-
161
- it "updates the javascript library" do
162
- FakeWeb.register_uri(:get, "https://raw.github.com/fnando/i18n-js/master/vendor/assets/javascripts/i18n.js", :body => "UPDATED")
163
-
164
- SimplesIdeias::I18n.setup!
165
- SimplesIdeias::I18n.update!
166
- File.read(SimplesIdeias::I18n.javascript_file).should == "UPDATED"
167
- end
168
-
169
- describe "#export_dir" do
170
- it "detects asset pipeline support" do
171
- SimplesIdeias::I18n.stub :has_asset_pipeline? => true
172
- SimplesIdeias::I18n.export_dir == "vendor/assets/javascripts"
173
- end
174
-
175
- it "detects older Rails" do
176
- SimplesIdeias::I18n.stub :has_asset_pipeline? => false
177
- SimplesIdeias::I18n.export_dir.to_s.should == "public/javascripts"
178
- end
179
- end
180
-
181
- describe "#has_asset_pipeline?" do
182
- it "detects support" do
183
- Rails.stub_chain(:configuration, :assets, :enabled => true)
184
- SimplesIdeias::I18n.should have_asset_pipeline
185
- end
186
-
187
- it "skips support" do
188
- SimplesIdeias::I18n.should_not have_asset_pipeline
189
- end
190
- end
191
-
192
- private
193
- # Set the configuration as the current one
194
- def set_config(path)
195
- config = HashWithIndifferentAccess.new(YAML.load_file(File.dirname(__FILE__) + "/resources/#{path}"))
196
- SimplesIdeias::I18n.stub(:config? => true)
197
- SimplesIdeias::I18n.stub(:config => config)
198
- end
199
-
200
- # Shortcut to SimplesIdeias::I18n.translations
201
- def translations
202
- SimplesIdeias::I18n.translations
203
- end
204
- end
205
-
@@ -1,3 +0,0 @@
1
- translations:
2
- - file: "public/javascripts/i18n/%{locale}.js"
3
- only: '*'
@@ -1,6 +0,0 @@
1
- # Find more details about this configuration file at http://github.com/fnando/i18n-js
2
- translations:
3
- - file: "public/javascripts/bitsnpieces.js"
4
- only:
5
- - "*.date.formats"
6
- - "*.number.currency"
@@ -1,450 +0,0 @@
1
- // Instantiate the object
2
- var I18n = I18n || {};
3
-
4
- // Set default locale to english
5
- I18n.defaultLocale = "en";
6
-
7
- // Set default handling of translation fallbacks to false
8
- I18n.fallbacks = false;
9
-
10
- // Set default separator
11
- I18n.defaultSeparator = ".";
12
-
13
- // Set current locale to null
14
- I18n.locale = null;
15
-
16
- // Set the placeholder format. Accepts `{{placeholder}}` and `%{placeholder}`.
17
- I18n.PLACEHOLDER = /(?:\{\{|%\{)(.*?)(?:\}\}?)/gm;
18
-
19
- I18n.isValidNode = function(obj, node, undefined) {
20
- return obj[node] !== null && obj[node] !== undefined;
21
- }
22
-
23
- I18n.lookup = function(scope, options) {
24
- var options = options || {}
25
- , lookupInitialScope = scope
26
- , translations = this.prepareOptions(I18n.translations)
27
- , messages = translations[options.locale || I18n.currentLocale()]
28
- , options = this.prepareOptions(options)
29
- , currentScope
30
- ;
31
-
32
- if (!messages){
33
- return;
34
- }
35
-
36
- if (typeof(scope) == "object") {
37
- scope = scope.join(this.defaultSeparator);
38
- }
39
-
40
- if (options.scope) {
41
- scope = options.scope.toString() + this.defaultSeparator + scope;
42
- }
43
-
44
- scope = scope.split(this.defaultSeparator);
45
-
46
- while (scope.length > 0) {
47
- currentScope = scope.shift();
48
- messages = messages[currentScope];
49
-
50
- if (!messages) {
51
- if (I18n.fallbacks && !options.fallback) {
52
- messages = I18n.lookup(lookupInitialScope, this.prepareOptions({ locale: I18n.defaultLocale, fallback: true }, options));
53
- }
54
- break;
55
- }
56
- }
57
-
58
- if (!messages && this.isValidNode(options, "defaultValue")) {
59
- messages = options.defaultValue;
60
- }
61
-
62
- return messages;
63
- };
64
-
65
- // Merge serveral hash options, checking if value is set before
66
- // overwriting any value. The precedence is from left to right.
67
- //
68
- // I18n.prepareOptions({name: "John Doe"}, {name: "Mary Doe", role: "user"});
69
- // #=> {name: "John Doe", role: "user"}
70
- //
71
- I18n.prepareOptions = function() {
72
- var options = {}
73
- , opts
74
- , count = arguments.length
75
- ;
76
-
77
- for (var i = 0; i < count; i++) {
78
- opts = arguments[i];
79
-
80
- if (!opts) {
81
- continue;
82
- }
83
-
84
- for (var key in opts) {
85
- if (!this.isValidNode(options, key)) {
86
- options[key] = opts[key];
87
- }
88
- }
89
- }
90
-
91
- return options;
92
- };
93
-
94
- I18n.interpolate = function(message, options) {
95
- options = this.prepareOptions(options);
96
- var matches = message.match(this.PLACEHOLDER)
97
- , placeholder
98
- , value
99
- , name
100
- ;
101
-
102
- if (!matches) {
103
- return message;
104
- }
105
-
106
- for (var i = 0; placeholder = matches[i]; i++) {
107
- name = placeholder.replace(this.PLACEHOLDER, "$1");
108
-
109
- value = options[name];
110
-
111
- if (!this.isValidNode(options, name)) {
112
- value = "[missing " + placeholder + " value]";
113
- }
114
-
115
- regex = new RegExp(placeholder.replace(/\{/gm, "\\{").replace(/\}/gm, "\\}"));
116
- message = message.replace(regex, value);
117
- }
118
-
119
- return message;
120
- };
121
-
122
- I18n.translate = function(scope, options) {
123
- options = this.prepareOptions(options);
124
- var translation = this.lookup(scope, options);
125
-
126
- try {
127
- if (typeof(translation) == "object") {
128
- if (typeof(options.count) == "number") {
129
- return this.pluralize(options.count, scope, options);
130
- } else {
131
- return translation;
132
- }
133
- } else {
134
- return this.interpolate(translation, options);
135
- }
136
- } catch(err) {
137
- return this.missingTranslation(scope);
138
- }
139
- };
140
-
141
- I18n.localize = function(scope, value) {
142
- switch (scope) {
143
- case "currency":
144
- return this.toCurrency(value);
145
- case "number":
146
- scope = this.lookup("number.format");
147
- return this.toNumber(value, scope);
148
- case "percentage":
149
- return this.toPercentage(value);
150
- default:
151
- if (scope.match(/^(date|time)/)) {
152
- return this.toTime(scope, value);
153
- } else {
154
- return value.toString();
155
- }
156
- }
157
- };
158
-
159
- I18n.parseDate = function(date) {
160
- var matches, convertedDate;
161
-
162
- // we have a date, so just return it.
163
- if (typeof(date) == "object") {
164
- return date;
165
- };
166
-
167
- // it matches the following formats:
168
- // yyyy-mm-dd
169
- // yyyy-mm-dd[ T]hh:mm::ss
170
- // yyyy-mm-dd[ T]hh:mm::ss
171
- // yyyy-mm-dd[ T]hh:mm::ssZ
172
- // yyyy-mm-dd[ T]hh:mm::ss+0000
173
- //
174
- matches = date.toString().match(/(\d{4})-(\d{2})-(\d{2})(?:[ T](\d{2}):(\d{2}):(\d{2}))?(Z|\+0000)?/);
175
-
176
- if (matches) {
177
- for (var i = 1; i <= 6; i++) {
178
- matches[i] = parseInt(matches[i], 10) || 0;
179
- }
180
-
181
- // month starts on 0
182
- matches[2] -= 1;
183
-
184
- if (matches[7]) {
185
- convertedDate = new Date(Date.UTC(matches[1], matches[2], matches[3], matches[4], matches[5], matches[6]));
186
- } else {
187
- convertedDate = new Date(matches[1], matches[2], matches[3], matches[4], matches[5], matches[6]);
188
- }
189
- } else if (typeof(date) == "number") {
190
- // UNIX timestamp
191
- convertedDate = new Date();
192
- convertedDate.setTime(date);
193
- } else if (date.match(/\d+ \d+:\d+:\d+ [+-]\d+ \d+/)) {
194
- // a valid javascript format with timezone info
195
- convertedDate = new Date();
196
- convertedDate.setTime(Date.parse(date))
197
- } else {
198
- // an arbitrary javascript string
199
- convertedDate = new Date();
200
- convertedDate.setTime(Date.parse(date));
201
- }
202
-
203
- return convertedDate;
204
- };
205
-
206
- I18n.toTime = function(scope, d) {
207
- var date = this.parseDate(d)
208
- , format = this.lookup(scope)
209
- ;
210
-
211
- if (date.toString().match(/invalid/i)) {
212
- return date.toString();
213
- }
214
-
215
- if (!format) {
216
- return date.toString();
217
- }
218
-
219
- return this.strftime(date, format);
220
- };
221
-
222
- I18n.strftime = function(date, format) {
223
- var options = this.lookup("date");
224
-
225
- if (!options) {
226
- return date.toString();
227
- }
228
-
229
- options.meridian = options.meridian || ["AM", "PM"];
230
-
231
- var weekDay = date.getDay()
232
- , day = date.getDate()
233
- , year = date.getFullYear()
234
- , month = date.getMonth() + 1
235
- , hour = date.getHours()
236
- , hour12 = hour
237
- , meridian = hour > 11 ? 1 : 0
238
- , secs = date.getSeconds()
239
- , mins = date.getMinutes()
240
- , offset = date.getTimezoneOffset()
241
- , absOffsetHours = Math.floor(Math.abs(offset / 60))
242
- , absOffsetMinutes = Math.abs(offset) - (absOffsetHours * 60)
243
- , timezoneoffset = (offset > 0 ? "-" : "+") + (absOffsetHours.toString().length < 2 ? "0" + absOffsetHours : absOffsetHours) + (absOffsetMinutes.toString().length < 2 ? "0" + absOffsetMinutes : absOffsetMinutes)
244
- ;
245
-
246
- if (hour12 > 12) {
247
- hour12 = hour12 - 12;
248
- } else if (hour12 === 0) {
249
- hour12 = 12;
250
- }
251
-
252
- var padding = function(n) {
253
- var s = "0" + n.toString();
254
- return s.substr(s.length - 2);
255
- };
256
-
257
- var f = format;
258
- f = f.replace("%a", options.abbr_day_names[weekDay]);
259
- f = f.replace("%A", options.day_names[weekDay]);
260
- f = f.replace("%b", options.abbr_month_names[month]);
261
- f = f.replace("%B", options.month_names[month]);
262
- f = f.replace("%d", padding(day));
263
- f = f.replace("%e", day);
264
- f = f.replace("%-d", day);
265
- f = f.replace("%H", padding(hour));
266
- f = f.replace("%-H", hour);
267
- f = f.replace("%I", padding(hour12));
268
- f = f.replace("%-I", hour12);
269
- f = f.replace("%m", padding(month));
270
- f = f.replace("%-m", month);
271
- f = f.replace("%M", padding(mins));
272
- f = f.replace("%-M", mins);
273
- f = f.replace("%p", options.meridian[meridian]);
274
- f = f.replace("%S", padding(secs));
275
- f = f.replace("%-S", secs);
276
- f = f.replace("%w", weekDay);
277
- f = f.replace("%y", padding(year));
278
- f = f.replace("%-y", padding(year).replace(/^0+/, ""));
279
- f = f.replace("%Y", year);
280
- f = f.replace("%z", timezoneoffset);
281
-
282
- return f;
283
- };
284
-
285
- I18n.toNumber = function(number, options) {
286
- options = this.prepareOptions(
287
- options,
288
- this.lookup("number.format"),
289
- {precision: 3, separator: ".", delimiter: ",", strip_insignificant_zeros: false}
290
- );
291
-
292
- var negative = number < 0
293
- , string = Math.abs(number).toFixed(options.precision).toString()
294
- , parts = string.split(".")
295
- , precision
296
- , buffer = []
297
- , formattedNumber
298
- ;
299
-
300
- number = parts[0];
301
- precision = parts[1];
302
-
303
- while (number.length > 0) {
304
- buffer.unshift(number.substr(Math.max(0, number.length - 3), 3));
305
- number = number.substr(0, number.length -3);
306
- }
307
-
308
- formattedNumber = buffer.join(options.delimiter);
309
-
310
- if (options.precision > 0) {
311
- formattedNumber += options.separator + parts[1];
312
- }
313
-
314
- if (negative) {
315
- formattedNumber = "-" + formattedNumber;
316
- }
317
-
318
- if (options.strip_insignificant_zeros) {
319
- var regex = {
320
- separator: new RegExp(options.separator.replace(/\./, "\\.") + "$")
321
- , zeros: /0+$/
322
- };
323
-
324
- formattedNumber = formattedNumber
325
- .replace(regex.zeros, "")
326
- .replace(regex.separator, "")
327
- ;
328
- }
329
-
330
- return formattedNumber;
331
- };
332
-
333
- I18n.toCurrency = function(number, options) {
334
- options = this.prepareOptions(
335
- options,
336
- this.lookup("number.currency.format"),
337
- this.lookup("number.format"),
338
- {unit: "$", precision: 2, format: "%u%n", delimiter: ",", separator: "."}
339
- );
340
-
341
- number = this.toNumber(number, options);
342
- number = options.format
343
- .replace("%u", options.unit)
344
- .replace("%n", number)
345
- ;
346
-
347
- return number;
348
- };
349
-
350
- I18n.toHumanSize = function(number, options) {
351
- var kb = 1024
352
- , size = number
353
- , iterations = 0
354
- , unit
355
- , precision
356
- ;
357
-
358
- while (size >= kb && iterations < 4) {
359
- size = size / kb;
360
- iterations += 1;
361
- }
362
-
363
- if (iterations === 0) {
364
- unit = this.t("number.human.storage_units.units.byte", {count: size});
365
- precision = 0;
366
- } else {
367
- unit = this.t("number.human.storage_units.units." + [null, "kb", "mb", "gb", "tb"][iterations]);
368
- precision = (size - Math.floor(size) === 0) ? 0 : 1;
369
- }
370
-
371
- options = this.prepareOptions(
372
- options,
373
- {precision: precision, format: "%n%u", delimiter: ""}
374
- );
375
-
376
- number = this.toNumber(size, options);
377
- number = options.format
378
- .replace("%u", unit)
379
- .replace("%n", number)
380
- ;
381
-
382
- return number;
383
- };
384
-
385
- I18n.toPercentage = function(number, options) {
386
- options = this.prepareOptions(
387
- options,
388
- this.lookup("number.percentage.format"),
389
- this.lookup("number.format"),
390
- {precision: 3, separator: ".", delimiter: ""}
391
- );
392
-
393
- number = this.toNumber(number, options);
394
- return number + "%";
395
- };
396
-
397
- I18n.pluralize = function(count, scope, options) {
398
- var translation;
399
-
400
- try {
401
- translation = this.lookup(scope, options);
402
- } catch (error) {}
403
-
404
- if (!translation) {
405
- return this.missingTranslation(scope);
406
- }
407
-
408
- var message;
409
- options = this.prepareOptions(options);
410
- options.count = count.toString();
411
-
412
- switch(Math.abs(count)) {
413
- case 0:
414
- message = this.isValidNode(translation, "zero") ? translation.zero :
415
- this.isValidNode(translation, "none") ? translation.none :
416
- this.isValidNode(translation, "other") ? translation.other :
417
- this.missingTranslation(scope, "zero");
418
- break;
419
- case 1:
420
- message = this.isValidNode(translation, "one") ? translation.one : this.missingTranslation(scope, "one");
421
- break;
422
- default:
423
- message = this.isValidNode(translation, "other") ? translation.other : this.missingTranslation(scope, "other");
424
- }
425
-
426
- return this.interpolate(message, options);
427
- };
428
-
429
- I18n.missingTranslation = function() {
430
- var message = '[missing "' + this.currentLocale()
431
- , count = arguments.length
432
- ;
433
-
434
- for (var i = 0; i < count; i++) {
435
- message += "." + arguments[i];
436
- }
437
-
438
- message += '" translation]';
439
-
440
- return message;
441
- };
442
-
443
- I18n.currentLocale = function() {
444
- return (I18n.locale || I18n.defaultLocale);
445
- };
446
-
447
- // shortcuts
448
- I18n.t = I18n.translate;
449
- I18n.l = I18n.localize;
450
- I18n.p = I18n.pluralize;