i18n-js 0.1.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.
@@ -0,0 +1,10 @@
1
+ module SimplesIdeias
2
+ module I18n
3
+ module Version
4
+ MAJOR = 0
5
+ MINOR = 1
6
+ PATCH = 0
7
+ STRING = "#{MAJOR}.#{MINOR}.#{PATCH}"
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,16 @@
1
+ namespace :i18n do
2
+ desc "Copy i18n.js and configuration file"
3
+ task :setup => :environment do
4
+ SimplesIdeias::I18n.setup!
5
+ end
6
+
7
+ desc "Export the messages files"
8
+ task :export => :environment do
9
+ SimplesIdeias::I18n.export!
10
+ end
11
+
12
+ desc "Update the JavaScript library"
13
+ task :update => :environment do
14
+ SimplesIdeias::I18n.update!
15
+ end
16
+ end
@@ -0,0 +1,4 @@
1
+ # Find more details about this configuration file at http://github.com/fnando/i18n-js
2
+ translations:
3
+ - file: "public/javascripts/translations.js"
4
+ only: "*"
data/source/i18n.js ADDED
@@ -0,0 +1,341 @@
1
+ // Instantiate the object
2
+ var I18n = I18n || {};
3
+
4
+ // Set default locale to english
5
+ I18n.defaultLocale = "en";
6
+
7
+ // Set current locale to null
8
+ I18n.locale = null;
9
+
10
+ // Set the placeholder format. Accepts `{{placeholder}}` and `%{placeholder}`.
11
+ I18n.PLACEHOLDER = /(?:\{\{|%\{)(.*?)(?:\}\}?)/gm;
12
+
13
+ I18n.lookup = function(scope, options) {
14
+ var translations = this.prepareOptions(I18n.translations);
15
+ var messages = translations[I18n.currentLocale()];
16
+ options = this.prepareOptions(options);
17
+
18
+ if (!messages) {
19
+ return;
20
+ }
21
+
22
+ if (typeof(scope) == "object") {
23
+ scope = scope.join(".");
24
+ }
25
+
26
+ if (options.scope) {
27
+ scope = options.scope.toString() + "." + scope;
28
+ }
29
+
30
+ scope = scope.split(".");
31
+
32
+ while (scope.length > 0) {
33
+ var currentScope = scope.shift();
34
+ messages = messages[currentScope];
35
+
36
+ if (!messages) {
37
+ break;
38
+ }
39
+ }
40
+
41
+ if (!messages && options.defaultValue != null && options.defaultValue != undefined) {
42
+ messages = options.defaultValue;
43
+ }
44
+
45
+ return messages;
46
+ };
47
+
48
+ // Merge serveral hash options, checking if value is set before
49
+ // overwriting any value. The precedence is from left to right.
50
+ //
51
+ // I18n.prepareOptions({name: "John Doe"}, {name: "Mary Doe", role: "user"});
52
+ // #=> {name: "John Doe", role: "user"}
53
+ //
54
+ I18n.prepareOptions = function() {
55
+ var options = {};
56
+ var opts;
57
+ var count = arguments.length;
58
+
59
+ for (var i = 0; i < count; i++) {
60
+ opts = arguments[i];
61
+
62
+ if (!opts) {
63
+ continue;
64
+ }
65
+
66
+ for (var key in opts) {
67
+ if (options[key] == undefined || options[key] == null) {
68
+ options[key] = opts[key];
69
+ }
70
+ }
71
+ }
72
+
73
+ return options;
74
+ };
75
+
76
+ I18n.interpolate = function(message, options) {
77
+ options = this.prepareOptions(options);
78
+ var matches = message.match(this.PLACEHOLDER);
79
+
80
+ if (!matches) {
81
+ return message;
82
+ }
83
+
84
+ var placeholder, value, name;
85
+
86
+ for (var i = 0; placeholder = matches[i]; i++) {
87
+ console.debug(placeholder)
88
+ name = placeholder.replace(this.PLACEHOLDER, "$1");
89
+ console.debug(name)
90
+
91
+ value = options[name];
92
+
93
+ if (options[name] == null || options[name] == undefined) {
94
+ value = "[missing " + placeholder + " value]";
95
+ }
96
+
97
+ regex = new RegExp(placeholder.replace(/\{/gm, "\\{").replace(/\}/gm, "\\}"));
98
+ message = message.replace(regex, value);
99
+ }
100
+
101
+ return message;
102
+ };
103
+
104
+ I18n.translate = function(scope, options) {
105
+ options = this.prepareOptions(options);
106
+ var translation = this.lookup(scope, options);
107
+
108
+ try {
109
+ if (typeof(translation) == "object") {
110
+ if (typeof(options.count) == "number") {
111
+ return this.pluralize(options.count, scope, options);
112
+ } else {
113
+ return translation;
114
+ }
115
+ } else {
116
+ return this.interpolate(translation, options);
117
+ }
118
+ } catch(err) {
119
+ return this.missingTranslation(scope);
120
+ }
121
+ };
122
+
123
+ I18n.localize = function(scope, value) {
124
+ switch (scope) {
125
+ case "currency":
126
+ return this.toCurrency(value);
127
+ case "number":
128
+ scope = this.lookup("number.format");
129
+ return this.toNumber(value, scope);
130
+ case "percentage":
131
+ return this.toPercentage(value);
132
+ default:
133
+ if (scope.match(/^(date|time)/)) {
134
+ return this.toTime(scope, value);
135
+ } else {
136
+ return value.toString();
137
+ }
138
+ }
139
+ };
140
+
141
+ I18n.parseDate = function(d) {
142
+ var matches, date;
143
+
144
+ if (matches = d.toString().match(/(\d{4})-(\d{2})-(\d{2})(?:[ |T](\d{2}):(\d{2}):(\d{2}))?(Z)?/)) {
145
+ // date/time strings: yyyy-mm-dd hh:mm:ss or yyyy-mm-dd or yyyy-mm-ddThh:mm:ssZ
146
+ for (var i = 1; i <= 6; i++) {
147
+ matches[i] = parseInt(matches[i], 10) || 0;
148
+ }
149
+
150
+ // month starts on 0
151
+ matches[2] -= 1;
152
+
153
+ if (matches[7]) {
154
+ date = new Date(Date.UTC(matches[1], matches[2], matches[3], matches[4], matches[5], matches[6]));
155
+ } else {
156
+ date = new Date(matches[1], matches[2], matches[3], matches[4], matches[5], matches[6]);
157
+ }
158
+ } else if (typeof(d) == "number") {
159
+ // UNIX timestamp
160
+ date = new Date();
161
+ date.setTime(d);
162
+ } else {
163
+ // an arbitrary javascript string
164
+ date = new Date();
165
+ date.setTime(Date.parse(d));
166
+ }
167
+
168
+ return date;
169
+ };
170
+
171
+ I18n.toTime = function(scope, d) {
172
+ var date = this.parseDate(d);
173
+ var format = this.lookup(scope);
174
+
175
+ if (date.toString().match(/invalid/i)) {
176
+ return date.toString();
177
+ }
178
+
179
+ if (!format) {
180
+ return date.toString();
181
+ }
182
+
183
+ return this.strftime(date, format);
184
+ };
185
+
186
+ I18n.strftime = function(date, format) {
187
+ var options = this.lookup("date");
188
+
189
+ if (!options) {
190
+ return date.toString();
191
+ }
192
+
193
+ var weekDay = date.getDay();
194
+ var day = date.getDate();
195
+ var year = date.getFullYear();
196
+ var month = date.getMonth() + 1;
197
+ var hour = date.getHours();
198
+ var hour12 = hour;
199
+ var meridian = hour > 12? "PM" : "AM";
200
+ var secs = date.getSeconds();
201
+ var mins = date.getMinutes();
202
+ var offset = date.getTimezoneOffset();
203
+ var absOffsetHours = Math.floor(Math.abs(offset / 60));
204
+ var absOffsetMinutes = Math.abs(offset) - (absOffsetHours * 60);
205
+ var timezoneoffset = (offset > 0 ? "-" : "+") + (absOffsetHours.toString().length < 2 ? "0" + absOffsetHours : absOffsetHours) + (absOffsetMinutes.toString().length < 2 ? "0" + absOffsetMinutes : absOffsetMinutes);
206
+
207
+ if (hour12 > 12) {
208
+ hour12 = hour12 - 12;
209
+ }
210
+
211
+ var padding = function(n) {
212
+ var s = "0" + n.toString();
213
+ return s.substr(s.length - 2);
214
+ }
215
+
216
+ var f = format;
217
+ f = f.replace("%a", options["abbr_day_names"][weekDay]);
218
+ f = f.replace("%A", options["day_names"][weekDay]);
219
+ f = f.replace("%b", options["abbr_month_names"][month]);
220
+ f = f.replace("%B", options["month_names"][month]);
221
+ f = f.replace("%d", padding(day));
222
+ f = f.replace("%-d", day);
223
+ f = f.replace("%H", padding(hour));
224
+ f = f.replace("%-H", hour);
225
+ f = f.replace("%I", padding(hour12));
226
+ f = f.replace("%-I", hour12);
227
+ f = f.replace("%m", padding(month));
228
+ f = f.replace("%-m", month);
229
+ f = f.replace("%M", padding(mins));
230
+ f = f.replace("%-M", mins);
231
+ f = f.replace("%p", meridian);
232
+ f = f.replace("%S", padding(secs));
233
+ f = f.replace("%-S", secs);
234
+ f = f.replace("%w", weekDay);
235
+ f = f.replace("%y", padding(year));
236
+ f = f.replace("%-y", padding(year).replace(/^0+/, ""));
237
+ f = f.replace("%Y", year);
238
+ f = f.replace("%z", timezoneoffset);
239
+
240
+ return f;
241
+ };
242
+
243
+ I18n.toNumber = function(number, options) {
244
+ options = this.prepareOptions(
245
+ options,
246
+ this.lookup("number.format"),
247
+ {precision: 3, separator: ".", delimiter: ","}
248
+ );
249
+
250
+ var string = number.toFixed(options["precision"]).toString();
251
+ var parts = string.split(".");
252
+
253
+ number = parts[0];
254
+ var precision = parts[1];
255
+
256
+ var n = [];
257
+
258
+ while (number.length > 0) {
259
+ n.unshift(number.substr(Math.max(0, number.length - 3), 3));
260
+ number = number.substr(0, number.length -3);
261
+ }
262
+
263
+ var formattedNumber = n.join(options["delimiter"]);
264
+
265
+ if (options["precision"] > 0) {
266
+ formattedNumber += options["separator"] + parts[1];
267
+ }
268
+
269
+ return formattedNumber;
270
+ };
271
+
272
+ I18n.toCurrency = function(number, options) {
273
+ options = this.prepareOptions(
274
+ options,
275
+ this.lookup("number.currency.format"),
276
+ this.lookup("number.format"),
277
+ {unit: "$", precision: 2, format: "%u%n", delimiter: ",", separator: "."}
278
+ );
279
+
280
+ number = this.toNumber(number, options);
281
+ number = options["format"]
282
+ .replace("%u", options["unit"])
283
+ .replace("%n", number);
284
+
285
+ return number;
286
+ };
287
+
288
+ I18n.toPercentage = function(number, options) {
289
+ options = this.prepareOptions(
290
+ options,
291
+ this.lookup("number.percentage.format"),
292
+ this.lookup("number.format"),
293
+ {precision: 3, separator: ".", delimiter: ""}
294
+ );
295
+
296
+ number = this.toNumber(number, options);
297
+ return number + "%";
298
+ };
299
+
300
+ I18n.pluralize = function(count, scope, options) {
301
+ var translation = this.lookup(scope, options);
302
+
303
+ var message;
304
+ options = this.prepareOptions(options);
305
+ options["count"] = count.toString();
306
+
307
+ switch(Math.abs(count)) {
308
+ case 0:
309
+ message = translation["zero"] || translation["none"] || translation["other"] || this.missingTranslation(scope, "zero");
310
+ break;
311
+ case 1:
312
+ message = translation["one"] || this.missingTranslation(scope, "one");;
313
+ break;
314
+ default:
315
+ message = translation["other"] || this.missingTranslation(scope, "other");;
316
+ }
317
+
318
+ return this.interpolate(message, options);
319
+ };
320
+
321
+ I18n.missingTranslation = function() {
322
+ var message = '[missing "' + this.currentLocale();
323
+ var count = arguments.length;
324
+
325
+ for (var i = 0; i < count; i++) {
326
+ message += "." + arguments[i];
327
+ }
328
+
329
+ message += '" translation]';
330
+
331
+ return message;
332
+ };
333
+
334
+ I18n.currentLocale = function() {
335
+ return (I18n.locale || I18n.defaultLocale);
336
+ };
337
+
338
+ // shortcuts
339
+ I18n.t = I18n.translate;
340
+ I18n.l = I18n.localize;
341
+ I18n.p = I18n.pluralize;
@@ -0,0 +1,50 @@
1
+ <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
2
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
3
+ <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
4
+ <head>
5
+ <title>JavaScript unit test file</title>
6
+ <meta http-equiv="content-type" content="text/html; charset=utf-8" />
7
+ <script src="jsunittest/jsunittest.js" type="text/javascript"></script>
8
+ <link rel="stylesheet" href="jsunittest/unittest.css" type="text/css" />
9
+
10
+ <style type="text/css" media="screen">
11
+ #logger p {
12
+ background: #ffc;
13
+ padding: 5px;
14
+ }
15
+ </style>
16
+ <script type="text/javascript" charset="utf-8">
17
+ function log(name, message) {
18
+ var tag = document.getElementById("logger");
19
+ message = message.toString();
20
+ message = message.replace(/&/gm, "&amp;");
21
+ message = message.replace(/</gm, "&lt;");
22
+ message = message.replace(/>/gm, "&gt;");
23
+ tag.innerHTML += "<p><strong>" + name + ":</strong> " + message + "</p>";
24
+ }
25
+ </script>
26
+ <script src="../source/i18n.js" type="text/javascript"></script>
27
+ </head>
28
+ <body>
29
+
30
+ <div id="content">
31
+ <div id="header">
32
+ <h1>JavaScript unit test file</h1>
33
+ <p>
34
+ This file tests <strong>i18n.js</strong>.
35
+ </p>
36
+ </div>
37
+
38
+ <!-- Log output (one per Runner, via {testLog: "testlog"} option)-->
39
+ <div id="testlog"></div>
40
+
41
+ <!-- General debugger -->
42
+ <div id="logger"></div>
43
+
44
+ <!-- Put sample/test html here -->
45
+ <div id="sample">
46
+ </div>
47
+ </div>
48
+ <script src="i18n-test.js" type="text/javascript" charset="utf-8"></script>
49
+ </body>
50
+ </html>
data/test/i18n-test.js ADDED
@@ -0,0 +1,668 @@
1
+ new Test.Unit.Runner({
2
+ setup: function() {
3
+ I18n.defaultLocale = "en";
4
+ I18n.locale = null;
5
+
6
+ I18n.translations = {
7
+ en: {
8
+ hello: "Hello World!",
9
+ greetings: {
10
+ stranger: "Hello stranger!",
11
+ name: "Hello {{name}}!"
12
+ },
13
+ profile: {
14
+ details: "{{name}} is {{age}}-years old"
15
+ },
16
+ inbox: {
17
+ one: "You have {{count}} message",
18
+ other: "You have {{count}} messages",
19
+ zero: "You have no messages"
20
+ },
21
+ unread: {
22
+ one: "You have 1 new message ({{unread}} unread)",
23
+ other: "You have {{count}} new messages ({{unread}} unread)",
24
+ zero: "You have no new messages ({{unread}} unread)"
25
+ },
26
+ number: null
27
+ },
28
+
29
+ pt: {
30
+ hello: "Olá Mundo!",
31
+ date: {
32
+ formats: {
33
+ "default": "%d/%m/%Y",
34
+ "short": "%d de %B",
35
+ "long": "%d de %B de %Y"
36
+ },
37
+ day_names: ["Domingo", "Segunda", "Terça", "Quarta", "Quinta", "Sexta", "Sábado"],
38
+ abbr_day_names: ["Dom", "Seg", "Ter", "Qua", "Qui", "Sex", "Sáb"],
39
+ month_names: [null, "Janeiro", "Fevereiro", "Março", "Abril", "Maio", "Junho", "Julho", "Agosto", "Setembro", "Outubro", "Novembro", "Dezembro"],
40
+ abbr_month_names: [null, "Jan", "Fev", "Mar", "Abr", "Mai", "Jun", "Jul", "Ago", "Set", "Out", "Nov", "Dez"]
41
+ },
42
+ number: {
43
+ percentage: {
44
+ format: {
45
+ delimiter: "",
46
+ separator: ",",
47
+ precision: 2
48
+ }
49
+ }
50
+ },
51
+ time: {
52
+ formats: {
53
+ "default": "%A, %d de %B de %Y, %H:%M h",
54
+ "short": "%d/%m, %H:%M h",
55
+ "long": "%A, %d de %B de %Y, %H:%M h"
56
+ },
57
+ am: "AM",
58
+ pm: "PM"
59
+ }
60
+ }
61
+ }
62
+ },
63
+
64
+ teardown: function() {
65
+ },
66
+
67
+ // Defaults
68
+ testDefaults: function() { with(this) {
69
+ assertEqual("en", I18n.defaultLocale);
70
+ assertEqual(null, I18n.locale);
71
+ assertEqual("en", I18n.currentLocale());
72
+ }},
73
+
74
+ // Custom locale
75
+ testCustomLocale: function() { with(this) {
76
+ I18n.locale = "pt";
77
+ assertEqual("pt", I18n.currentLocale());
78
+ }},
79
+
80
+ // Aliases methods
81
+ testAliasesMethods: function() { with(this) {
82
+ assertEqual(I18n.translate, I18n.t);
83
+ assertEqual(I18n.localize, I18n.l);
84
+ assertEqual(I18n.pluralize, I18n.p);
85
+ }},
86
+
87
+ // Translation for single scope
88
+ testTranslationForSingleScope: function() { with(this) {
89
+ assertEqual("Hello World!", I18n.translate("hello"));
90
+ }},
91
+
92
+ // Translation as object
93
+ testTranslationAsObject: function() { with(this) {
94
+ assertEqual("object", typeof I18n.translate("greetings"));
95
+ }},
96
+
97
+ // Translation with invalid scope shall not block
98
+ testTranslationWithInvalidScope: function() { with(this) {
99
+ assertEqual('[missing "en.invalid.scope.shall.not.block" translation]', I18n.translate("invalid.scope.shall.not.block"));
100
+ }},
101
+
102
+ // Translation for single scope on a custom locale
103
+ testTranslationForSingleScopeOnACustomLocale: function() { with(this) {
104
+ I18n.locale = "pt";
105
+ assertEqual("Olá Mundo!", I18n.translate("hello"));
106
+ }},
107
+
108
+ // Translation for multiple scopes
109
+ testTranslationForMultipleScopes: function() { with(this) {
110
+ assertEqual("Hello stranger!", I18n.translate("greetings.stranger"));
111
+ }},
112
+
113
+ // Single interpolation
114
+ testSingleInterpolation: function() { with(this) {
115
+ actual = I18n.translate("greetings.name", {name: "John Doe"});
116
+ assertEqual("Hello John Doe!", actual);
117
+ }},
118
+
119
+ // Multiple interpolations
120
+ testMultipleInterpolations: function() { with(this) {
121
+ actual = I18n.translate("profile.details", {name: "John Doe", age: 27});
122
+ assertEqual("John Doe is 27-years old", actual);
123
+ }},
124
+
125
+ // Translation with count option
126
+ testTranslationWithCountOption: function() { with(this) {
127
+ assertEqual("You have 1 message", I18n.translate("inbox", {count: 1}));
128
+ assertEqual("You have 5 messages", I18n.translate("inbox", {count: 5}));
129
+ assertEqual("You have no messages", I18n.translate("inbox", {count: 0}));
130
+ }},
131
+
132
+ // Translation with count option and multiple placeholders
133
+ testTranslationWithCountOptionAndMultiplePlaceholders: function() { with(this) {
134
+ actual = I18n.translate("unread", {unread: 5, count: 1});
135
+ assertEqual("You have 1 new message (5 unread)", actual);
136
+
137
+ actual = I18n.translate("unread", {unread: 2, count: 10});
138
+ assertEqual("You have 10 new messages (2 unread)", actual);
139
+
140
+ actual = I18n.translate("unread", {unread: 5, count: 0});
141
+ assertEqual("You have no new messages (5 unread)", actual);
142
+ }},
143
+
144
+ // Missing translation with count option
145
+ testMissingTranslationWithCountOption: function() { with(this) {
146
+ actual = I18n.translate("invalid", {count: 1});
147
+ assertEqual('[missing "en.invalid" translation]', actual);
148
+
149
+ I18n.translations.en.inbox.one = null;
150
+ actual = I18n.translate("inbox", {count: 1});
151
+ assertEqual('[missing "en.inbox.one" translation]', actual);
152
+ }},
153
+
154
+ // Pluralization
155
+ testPluralization: function() { with(this) {
156
+ assertEqual("You have 1 message", I18n.pluralize(1, "inbox"));
157
+ assertEqual("You have 5 messages", I18n.pluralize(5, "inbox"));
158
+ assertEqual("You have no messages", I18n.pluralize(0, "inbox"));
159
+ }},
160
+
161
+ // Pluralize should return "other" scope
162
+ testPlurationShouldReturnOtherScope: function() { with(this) {
163
+ I18n.translations["en"]["inbox"]["zero"] = null;
164
+ assertEqual("You have 0 messages", I18n.pluralize(0, "inbox"));
165
+ }},
166
+
167
+ // Pluralize should return "zero" scope
168
+ testPlurationShouldReturnZeroScope: function() { with(this) {
169
+ I18n.translations["en"]["inbox"]["zero"] = "No messages (zero)";
170
+ I18n.translations["en"]["inbox"]["none"] = "No messages (none)";
171
+
172
+ assertEqual("No messages (zero)", I18n.pluralize(0, "inbox"));
173
+ }},
174
+
175
+ // Pluralize should return "none" scope
176
+ testPlurationShouldReturnNoneScope: function() { with(this) {
177
+ I18n.translations["en"]["inbox"]["zero"] = null;
178
+ I18n.translations["en"]["inbox"]["none"] = "No messages (none)";
179
+
180
+ assertEqual("No messages (none)", I18n.pluralize(0, "inbox"));
181
+ }},
182
+
183
+ // Pluralize with negative values
184
+ testPluralizeWithNegativeValues: function() { with(this) {
185
+ assertEqual("You have -1 message", I18n.pluralize(-1, "inbox"));
186
+ assertEqual("You have -5 messages", I18n.pluralize(-5, "inbox"));
187
+ }},
188
+
189
+ // Pluralize with multiple placeholders
190
+ testPluralizeWithMultiplePlaceholders: function() { with(this) {
191
+ actual = I18n.pluralize(1, "unread", {unread: 5});
192
+ assertEqual("You have 1 new message (5 unread)", actual);
193
+
194
+ actual = I18n.pluralize(10, "unread", {unread: 2});
195
+ assertEqual("You have 10 new messages (2 unread)", actual);
196
+
197
+ actual = I18n.pluralize(0, "unread", {unread: 5});
198
+ assertEqual("You have no new messages (5 unread)", actual);
199
+ }},
200
+
201
+ // Numbers with default settings
202
+ testNumbersWithDefaultSettings: function() { with(this) {
203
+ assertEqual("1.000", I18n.toNumber(1));
204
+ assertEqual("12.000", I18n.toNumber(12));
205
+ assertEqual("123.000", I18n.toNumber(123));
206
+ assertEqual("1,234.000", I18n.toNumber(1234));
207
+ assertEqual("123,456.000", I18n.toNumber(123456));
208
+ assertEqual("1,234,567.000", I18n.toNumber(1234567));
209
+ assertEqual("12,345,678.000", I18n.toNumber(12345678));
210
+ }},
211
+
212
+ // Numbers with partial translation and default options
213
+ testNumbersWithPartialTranslationAndDefaultOptions: function() { with(this) {
214
+ I18n.translations.en.number = {
215
+ format: {
216
+ precision: 2
217
+ }
218
+ }
219
+
220
+ assertEqual("1,234.00", I18n.toNumber(1234));
221
+ }},
222
+
223
+ // Numbers with full translation and default options
224
+ testNumbersWithFullTranslationAndDefaultOptions: function() { with(this) {
225
+ I18n.translations.en.number = {
226
+ format: {
227
+ delimiter: ".",
228
+ separator: ",",
229
+ precision: 2
230
+ }
231
+ }
232
+
233
+ assertEqual("1.234,00", I18n.toNumber(1234));
234
+ }},
235
+
236
+ // Numbers with some custom options that should be merged with default options
237
+ testNumbersWithSomeCustomOptionsThatShouldBeMergedWithDefaultOptions: function() { with(this) {
238
+ assertEqual("1,234", I18n.toNumber(1234, {precision: 0}));
239
+ assertEqual("1,234-000", I18n.toNumber(1234, {separator: "-"}));
240
+ assertEqual("1-234.000", I18n.toNumber(1234, {delimiter: "-"}));
241
+ }},
242
+
243
+ // Numbers considering options
244
+ testNumbersConsideringOptions: function() { with(this) {
245
+ options = {
246
+ precision: 2,
247
+ separator: ",",
248
+ delimiter: "."
249
+ };
250
+
251
+ assertEqual("1,00", I18n.toNumber(1, options));
252
+ assertEqual("12,00", I18n.toNumber(12, options));
253
+ assertEqual("123,00", I18n.toNumber(123, options));
254
+ assertEqual("1.234,00", I18n.toNumber(1234, options));
255
+ assertEqual("123.456,00", I18n.toNumber(123456, options));
256
+ assertEqual("1.234.567,00", I18n.toNumber(1234567, options));
257
+ assertEqual("12.345.678,00", I18n.toNumber(12345678, options));
258
+ }},
259
+
260
+ // Numbers with different precisions
261
+ testNumbersWithDifferentPrecisions: function() { with(this) {
262
+ options = {separator: ".", delimiter: ","};
263
+
264
+ options["precision"] = 2;
265
+ assertEqual("1.98", I18n.toNumber(1.98, options));
266
+
267
+ options["precision"] = 3;
268
+ assertEqual("1.980", I18n.toNumber(1.98, options));
269
+
270
+ options["precision"] = 2;
271
+ assertEqual("1.99", I18n.toNumber(1.987, options));
272
+
273
+ options["precision"] = 1;
274
+ assertEqual("2.0", I18n.toNumber(1.98, options));
275
+
276
+ options["precision"] = 0;
277
+ assertEqual("2", I18n.toNumber(1.98, options));
278
+ }},
279
+
280
+ // Currency with default settings
281
+ testCurrencyWithDefaultSettings: function() { with(this) {
282
+ assertEqual("$100.99", I18n.toCurrency(100.99));
283
+ assertEqual("$1,000.99", I18n.toCurrency(1000.99));
284
+ }},
285
+
286
+ // Current with custom settings
287
+ testCurrencyWithCustomSettings: function() { with(this) {
288
+ I18n.translations.en.number = {
289
+ currency: {
290
+ format: {
291
+ format: "%n %u",
292
+ unit: "USD",
293
+ delimiter: ".",
294
+ separator: ",",
295
+ precision: 2
296
+ }
297
+ }
298
+ };
299
+
300
+ assertEqual("12,00 USD", I18n.toCurrency(12));
301
+ assertEqual("123,00 USD", I18n.toCurrency(123));
302
+ assertEqual("1.234,56 USD", I18n.toCurrency(1234.56));
303
+ }},
304
+
305
+ // Currency with custom settings and partial overriding
306
+ testCurrencyWithCustomSettingsAndPartialOverriding: function() { with(this) {
307
+ I18n.translations.en.number = {
308
+ currency: {
309
+ format: {
310
+ format: "%n %u",
311
+ unit: "USD",
312
+ delimiter: ".",
313
+ separator: ",",
314
+ precision: 2
315
+ }
316
+ }
317
+ };
318
+
319
+ assertEqual("12 USD", I18n.toCurrency(12, {precision: 0}));
320
+ assertEqual("123,00 bucks", I18n.toCurrency(123, {unit: "bucks"}));
321
+ }},
322
+
323
+ // Currency with some custom options that should be merged with default options
324
+ testCurrencyWithSomeCustomOptionsThatShouldBeMergedWithDefaultOptions: function() { with(this) {
325
+ assertEqual("$1,234", I18n.toCurrency(1234, {precision: 0}));
326
+ assertEqual("º1,234.00", I18n.toCurrency(1234, {unit: "º"}));
327
+ assertEqual("$1,234-00", I18n.toCurrency(1234, {separator: "-"}));
328
+ assertEqual("$1-234.00", I18n.toCurrency(1234, {delimiter: "-"}));
329
+ assertEqual("$ 1,234.00", I18n.toCurrency(1234, {format: "%u %n"}));
330
+ }},
331
+
332
+ // Localize numbers
333
+ testLocalizeNumbers: function() { with(this) {
334
+ assertEqual("1,234,567.000", I18n.localize("number", 1234567));
335
+ }},
336
+
337
+ // Localize currency
338
+ testLocalizeCurrency: function() { with(this) {
339
+ assertEqual("$1,234,567.00", I18n.localize("currency", 1234567));
340
+ }},
341
+
342
+ // Parse date
343
+ testParseDate: function() { with(this) {
344
+ expected = new Date(2009, 0, 24, 0, 0, 0);
345
+ assertEqual(expected.toString(), I18n.parseDate("2009-01-24").toString());
346
+
347
+ expected = new Date(2009, 0, 24, 0, 15, 0);
348
+ assertEqual(expected.toString(), I18n.parseDate("2009-01-24 00:15:00").toString());
349
+
350
+ expected = new Date(2009, 0, 24, 0, 0, 15);
351
+ assertEqual(expected.toString(), I18n.parseDate("2009-01-24 00:00:15").toString());
352
+
353
+ expected = new Date(2009, 0, 24, 15, 33, 44);
354
+ assertEqual(expected.toString(), I18n.parseDate("2009-01-24 15:33:44").toString());
355
+
356
+ expected = new Date(2009, 0, 24, 0, 0, 0);
357
+ assertEqual(expected.toString(), I18n.parseDate(expected.getTime()).toString());
358
+
359
+ expected = new Date(2009, 0, 24, 0, 0, 0);
360
+ assertEqual(expected.toString(), I18n.parseDate("01/24/2009").toString());
361
+
362
+ expected = new Date(2009, 0, 24, 14, 33, 55);
363
+ assertEqual(expected.toString(), I18n.parseDate(expected).toString());
364
+
365
+ expected = new Date(2009, 0, 24, 15, 33, 44);
366
+ assertEqual(expected.toString(), I18n.parseDate("2009-01-24T15:33:44").toString());
367
+
368
+ expected = new Date(Date.UTC(2009, 0, 24, 15, 33, 44));
369
+ assertEqual(expected.toString(), I18n.parseDate("2009-01-24T15:33:44Z").toString());
370
+ }},
371
+
372
+ // Date formatting
373
+ testDateFormatting: function() { with(this) {
374
+ I18n.locale = "pt";
375
+
376
+ // 2009-04-26 19:35:44 (Sunday)
377
+ var date = new Date(2009, 3, 26, 19, 35, 44);
378
+
379
+ // short week day
380
+ assertEqual("Dom", I18n.strftime(date, "%a"));
381
+
382
+ // full week day
383
+ assertEqual("Domingo", I18n.strftime(date, "%A"));
384
+
385
+ // short month
386
+ assertEqual("Abr", I18n.strftime(date, "%b"));
387
+
388
+ // full month
389
+ assertEqual("Abril", I18n.strftime(date, "%B"));
390
+
391
+ // day
392
+ assertEqual("26", I18n.strftime(date, "%d"));
393
+
394
+ // 24-hour
395
+ assertEqual("19", I18n.strftime(date, "%H"));
396
+
397
+ // 12-hour
398
+ assertEqual("07", I18n.strftime(date, "%I"));
399
+
400
+ // month
401
+ assertEqual("04", I18n.strftime(date, "%m"));
402
+
403
+ // minutes
404
+ assertEqual("35", I18n.strftime(date, "%M"));
405
+
406
+ // meridian
407
+ assertEqual("PM", I18n.strftime(date, "%p"));
408
+
409
+ // seconds
410
+ assertEqual("44", I18n.strftime(date, "%S"));
411
+
412
+ // week day
413
+ assertEqual("0", I18n.strftime(date, "%w"));
414
+
415
+ // short year
416
+ assertEqual("09", I18n.strftime(date, "%y"));
417
+
418
+ // full year
419
+ assertEqual("2009", I18n.strftime(date, "%Y"));
420
+ }},
421
+
422
+ // Date formatting without padding
423
+ testDateFormattingWithoutPadding: function() { with(this) {
424
+ I18n.locale = "pt";
425
+
426
+ // 2009-04-26 19:35:44 (Sunday)
427
+ var date = new Date(2009, 3, 9, 7, 8, 9);
428
+
429
+ // 24-hour without padding
430
+ assertEqual("7", I18n.strftime(date, "%-H"));
431
+
432
+ // 12-hour without padding
433
+ assertEqual("7", I18n.strftime(date, "%-I"));
434
+
435
+ // minutes without padding
436
+ assertEqual("8", I18n.strftime(date, "%-M"));
437
+
438
+ // seconds without padding
439
+ assertEqual("9", I18n.strftime(date, "%-S"));
440
+
441
+ // short year without padding
442
+ assertEqual("9", I18n.strftime(date, "%-y"));
443
+
444
+ // month without padding
445
+ assertEqual("4", I18n.strftime(date, "%-m"));
446
+
447
+ // day without padding
448
+ assertEqual("9", I18n.strftime(date, "%-d"));
449
+ }},
450
+
451
+ // Date formatting with padding
452
+ testDateFormattingWithPadding: function() { with(this) {
453
+ I18n.locale = "pt";
454
+
455
+ // 2009-04-26 19:35:44 (Sunday)
456
+ var date = new Date(2009, 3, 9, 7, 8, 9);
457
+
458
+ // 24-hour
459
+ assertEqual("07", I18n.strftime(date, "%H"));
460
+
461
+ // 12-hour
462
+ assertEqual("07", I18n.strftime(date, "%I"));
463
+
464
+ // minutes
465
+ assertEqual("08", I18n.strftime(date, "%M"));
466
+
467
+ // seconds
468
+ assertEqual("09", I18n.strftime(date, "%S"));
469
+
470
+ // short year
471
+ assertEqual("09", I18n.strftime(date, "%y"));
472
+
473
+ // month
474
+ assertEqual("04", I18n.strftime(date, "%m"));
475
+
476
+ // day
477
+ assertEqual("09", I18n.strftime(date, "%d"));
478
+ }},
479
+
480
+ // Date formatting with negative Timezone
481
+ testDateFormattingWithNegativeTimezone: function() { with(this) {
482
+ I18n.locale = "pt";
483
+
484
+ var date = new Date(2009, 3, 26, 19, 35, 44);
485
+
486
+ date.getTimezoneOffset = function() {
487
+ return 345;
488
+ };
489
+
490
+ assertMatch(/^(\+|-)[\d]{4}$/, I18n.strftime(date, "%z"));
491
+ assertEqual("-0545", I18n.strftime(date, "%z"));
492
+ }},
493
+
494
+ // Date formatting with positive Timezone
495
+ testDateFormattingWithPositiveTimezone: function() { with(this) {
496
+ I18n.locale = "pt";
497
+
498
+ var date = new Date(2009, 3, 26, 19, 35, 44);
499
+
500
+ date.getTimezoneOffset = function() {
501
+ return -345;
502
+ };
503
+
504
+ assertMatch(/^(\+|-)[\d]{4}$/, I18n.strftime(date, "%z"));
505
+ assertEqual("+0545", I18n.strftime(date, "%z"));
506
+ }},
507
+
508
+ // Localize date strings
509
+ testLocalizeDateStrings: function() { with(this) {
510
+ I18n.locale = "pt";
511
+
512
+ assertEqual("29/11/2009", I18n.localize("date.formats.default", "2009-11-29"));
513
+ assertEqual("07 de Janeiro", I18n.localize("date.formats.short", "2009-01-07"));
514
+ assertEqual("07 de Janeiro de 2009", I18n.localize("date.formats.long", "2009-01-07"));
515
+ }},
516
+
517
+ // Localize time strings
518
+ testLocalizeTimeStrings: function() { with(this) {
519
+ I18n.locale = "pt";
520
+ assertEqual("Domingo, 29 de Novembro de 2009, 15:07 h", I18n.localize("time.formats.default", "2009-11-29 15:07:59"));
521
+ assertEqual("07/01, 09:12 h", I18n.localize("time.formats.short", "2009-01-07 09:12:35"));
522
+ assertEqual("Domingo, 29 de Novembro de 2009, 15:07 h", I18n.localize("time.formats.long", "2009-11-29 15:07:59"));
523
+ }},
524
+
525
+ // Localize percentage
526
+ testLocalizePercentage: function() { with(this) {
527
+ I18n.locale = "pt";
528
+ assertEqual("123,45%", I18n.localize("percentage", 123.45));
529
+ }},
530
+
531
+
532
+
533
+ // Default value for simple translation
534
+ testDefaultValueForSimpleTranslation: function() { with(this) {
535
+ actual = I18n.translate("warning", {defaultValue: "Warning!"});
536
+ assertEqual("Warning!", actual);
537
+ }},
538
+
539
+ // Default value with interpolation
540
+ testDefaultValueWithInterpolation: function() { with(this) {
541
+ actual = I18n.translate("alert", {defaultValue: "Attention! {{message}}", message: "You're out of quota!"});
542
+ assertEqual("Attention! You're out of quota!", actual);
543
+ }},
544
+
545
+ // Default value should not be used when scope exist
546
+ testDefaultValueShouldNotBeUsedWhenScopeExist: function() { with(this) {
547
+ actual = I18n.translate("hello", {defaultValue: "What's up?"});
548
+ assertEqual("Hello World!", actual);
549
+ }},
550
+
551
+ // Default value for pluralize
552
+ testDefaultValueForPluralize: function() { with(this) {
553
+ options = {defaultValue: {
554
+ none: "No things here!",
555
+ one: "There is {{count}} thing here!",
556
+ other: "There are {{count}} things here!"
557
+ }};
558
+
559
+ assertEqual("No things here!", I18n.pluralize(0, "things", options));
560
+ assertEqual("There is 1 thing here!", I18n.pluralize(1, "things", options));
561
+ assertEqual("There are 5 things here!", I18n.pluralize(5, "things", options));
562
+ }},
563
+
564
+ // Default value for pluralize should not be used when scope exist
565
+ testDefaultValueForPluralizeShouldNotBeUsedWhenScopeExist: function() { with(this) {
566
+ options = {defaultValue: {
567
+ none: "No things here!",
568
+ one: "There is {{count}} thing here!",
569
+ other: "There are {{count}} things here!"
570
+ }};
571
+
572
+ assertEqual("You have no messages", I18n.pluralize(0, "inbox", options));
573
+ assertEqual("You have 1 message", I18n.pluralize(1, "inbox", options));
574
+ assertEqual("You have 5 messages", I18n.pluralize(5, "inbox", options));
575
+ }},
576
+
577
+ // Prepare options
578
+ testPrepareOptions: function() { with(this) {
579
+ options = I18n.prepareOptions(
580
+ {name: "Mary Doe"},
581
+ {name: "John Doe", role: "user"}
582
+ );
583
+
584
+ assertEqual("Mary Doe", options["name"]);
585
+ assertEqual("user", options["role"]);
586
+ }},
587
+
588
+ // Prepare options with multiple options
589
+ testPrepareOptionsWithMultipleOptions: function() { with(this) {
590
+ options = I18n.prepareOptions(
591
+ {name: "Mary Doe"},
592
+ {name: "John Doe", role: "user"},
593
+ {age: 33},
594
+ {email: "mary@doe.com", url: "http://marydoe.com"},
595
+ {role: "admin", email: "john@doe.com"}
596
+ );
597
+
598
+ assertEqual("Mary Doe", options["name"]);
599
+ assertEqual("user", options["role"]);
600
+ assertEqual(33, options["age"]);
601
+ assertEqual("mary@doe.com", options["email"]);
602
+ assertEqual("http://marydoe.com", options["url"]);
603
+ }},
604
+
605
+ // Prepare options should return an empty hash when values are null
606
+ testPrepareOptionsShouldReturnAnEmptyHashWhenValuesAreNull: function() { with(this) {
607
+ assertNotNullOrUndefined(I18n.prepareOptions(null, null));
608
+ }},
609
+
610
+ // Percentage with defaults
611
+ testPercentageWithDefaults: function() { with(this) {
612
+ assertEqual("1234.000%", I18n.toPercentage(1234));
613
+ }},
614
+
615
+ // Percentage with custom options
616
+ testPercentageWithCustomOptions: function() { with(this) {
617
+ assertEqual("1_234%", I18n.toPercentage(1234, {delimiter: "_", precision: 0}));
618
+ }},
619
+
620
+ // Percentage with translation
621
+ testPercentageWithTranslation: function() { with(this) {
622
+ I18n.translations.en.number = {
623
+ percentage: {
624
+ format: {
625
+ precision: 2,
626
+ delimiter: ".",
627
+ separator: ","
628
+ }
629
+ }
630
+ }
631
+
632
+ assertEqual("1.234,00%", I18n.toPercentage(1234));
633
+ }},
634
+
635
+ // Percentage with translation and custom options
636
+ testPercentageWithTranslationAndCustomOptions: function() { with(this) {
637
+ I18n.translations.en.number = {
638
+ percentage: {
639
+ format: {
640
+ precision: 2,
641
+ delimiter: ".",
642
+ separator: ","
643
+ }
644
+ }
645
+ }
646
+
647
+ assertEqual("1-234+0000%", I18n.toPercentage(1234, {precision: 4, delimiter: "-", separator: "+"}));
648
+ }},
649
+
650
+ // Scope option as string
651
+ testScopeOptionAsString: function() { with(this) {
652
+ actual = I18n.translate("stranger", {scope: "greetings"});
653
+ assertEqual("Hello stranger!", actual);
654
+ }},
655
+
656
+ // Scope as array
657
+ testScopeAsArray: function() { with(this) {
658
+ actual = I18n.translate(["greetings", "stranger"]);
659
+ assertEqual("Hello stranger!", actual);
660
+ }},
661
+
662
+ // New placeholder syntax
663
+ testNewPlaceholderSyntax: function() { with(this) {
664
+ I18n.translations["en"]["new_syntax"] = "Hi %{name}!";
665
+ actual = I18n.translate("new_syntax", {name: "John"});
666
+ assertEqual("Hi John!", actual);
667
+ }}
668
+ });