exvo_globalize 0.4.0 → 0.5.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/README.md CHANGED
@@ -67,11 +67,83 @@ Basic support for I18n in the javascript is included. In order for it to work yo
67
67
  Now you can use translations inside your javascript:
68
68
 
69
69
  ```js
70
- t("new")
71
- => "New"
70
+ t("hello")
71
+ => "Hello world!"
72
72
  ```
73
73
 
74
- The above, of course, expects you to have the `:new` key for the current `I18n.locale` in one of your `config/locales/*.yml` files.
74
+ The above, of course, expects you to have the `:hello` key for the current `I18n.locale` in one of your `config/locales/*.yml` files.
75
+
76
+
77
+ The following I18n features are supported in javascript:
78
+
79
+
80
+ ### Locale fallback
81
+
82
+ If there is no translation in the desired `locale`, there will be another check performed to see if there is a translation available in the `default_locale`. If there is, display it.
83
+
84
+ By default, when including two javascript tags in your application (see installation notes), you will have translations in the desired locale merged with translations for the default_locale available in the javascript.
85
+
86
+
87
+ ### Localization
88
+
89
+ All standard I18n keys (see `config/locales/*` in this gem’s directory) are used for various localization rules. Examples follows (for `:en` locale).
90
+
91
+
92
+ #### Numbers
93
+
94
+ ```js
95
+ I18n.toNumber(1234)
96
+ => "1,234.000"
97
+
98
+ l("number", 1234)
99
+ => "1,234.000"
100
+ ```
101
+
102
+ #### Percentages
103
+
104
+ ```js
105
+ I18n.toPercentage(1234)
106
+ => "1234.000%"
107
+
108
+ l("percentage", 1234)
109
+ => "1234.000%"
110
+ ```
111
+
112
+ #### Currencies
113
+
114
+ ```js
115
+ I18n.toCurrency(12)
116
+ => "12,00 USD"
117
+
118
+ l("currency", 12)
119
+ => "12,00 USD"
120
+ ```
121
+
122
+
123
+ #### Numbers in human sizes
124
+
125
+ ```js
126
+ I18n.toHumanSize(1024)
127
+ => "1KB"
128
+ ```
129
+
130
+
131
+ #### Dates and times
132
+
133
+ ```js
134
+ // uses ERB Ruby interpolation
135
+ I18n.strftime(I18n.parseDate("<%= Time.now %>"), "%Y/%m/%d %H:%M")
136
+ => "2011/09/14 12:03"
137
+
138
+ // uses formats from `config/locale/en.yml` file
139
+ I18n.l("date.formats.default", "Wed Sep 14 12:03:11 +0200 2011")
140
+ => "14-09-2011"
141
+
142
+ I18n.l("date.formats.long", "2011-09-14")
143
+ => "September 14, 2011"
144
+ ```
145
+
146
+ See `spec/javascripts/exvo_globalize_i18n_spec.js` for all available localization forms.
75
147
 
76
148
 
77
149
 
@@ -139,4 +211,13 @@ http://johnbintz.github.com/jasmine-headless-webkit/
139
211
 
140
212
 
141
213
 
214
+ ## Copyrights
215
+
216
+ exvo_globalize is based on the following projects:
217
+ https://github.com/svenfuchs/i18n
218
+ https://github.com/svenfuchs/rails-i18n/
219
+ https://github.com/toretore/babilu
220
+ https://github.com/spider-network/i18n-js
221
+
222
+
142
223
  Copyright © 2011 Exvo.com Development BV, released under the MIT license
data/Rakefile CHANGED
@@ -2,7 +2,7 @@ require 'bundler/gem_tasks'
2
2
 
3
3
  require 'rspec/core'
4
4
  require 'rspec/core/rake_task'
5
- import 'lib/tasks/jasmine.rake'
5
+
6
6
  RSpec::Core::RakeTask.new(:spec) do |spec|
7
7
  spec.pattern = FileList['spec/**/*_spec.rb']
8
8
  end
@@ -1,90 +1,374 @@
1
- var I18n = (function() {
2
- // Replace {{foo}} with obj.foo
3
- function interpolate(string, object) {
4
- return string.replace(/\{\{([^}]+)\}\}/g, function() {
5
- return object[arguments[1]] || arguments[0];
6
- });
7
- };
1
+ (function() {
8
2
 
9
- // Split "foo.bar" to ["foo", "bar"] if key is a string
10
- function keyToArray(key) {
11
- if(!key) {
12
- return [];
13
- }
14
- if(typeof key != "string") {
15
- return key;
16
- }
17
- return key.split('.');
18
- };
3
+ I18n = {};
19
4
 
20
- // Looks up a translation using an array of strings where the last
21
- // is the key and any string before that define the scope. The
22
- // current locale is always prepended and does not need to be
23
- // provided. The second parameter is an array of strings used as
24
- // defaults if the key can not be found. If a key starts with ":"
25
- // it is used as a key for lookup. This method does not perform
26
- // pluralization or interpolation.
27
- function lookup(keys, defaults) {
28
- var i = 0, value = I18n.translations;
29
- defaults = (typeof defaults === "string") ? [defaults] : (defaults || []);
30
- while(keys[i]) {
31
- value = value && value[keys[i]];
32
- i++;
33
- }
34
- if(value) {
35
- return value;
36
- } else {
37
- if(defaults.length === 0) {
38
- return null;
39
- } else if (defaults[0].substr(0,1) === ':') {
40
- return lookup(keys.slice(0, keys.length - 1).concat(keyToArray(defaults[0].substr(1))), defaults.slice(1));
41
- } else {
42
- return defaults[0];
43
- }
5
+ var interpolatePattern = /%\{([^}]+)\}/g;
6
+
7
+ // Replace %{foo} with obj.foo
8
+ function interpolate(str, obj) {
9
+ return str.replace(interpolatePattern, function() {
10
+ return typeof obj[arguments[1]] == 'undefined' ? arguments[0] : obj[arguments[1]];
11
+ });
12
+ };
13
+
14
+ // Split "foo.bar" to ["foo", "bar"] if key is a string
15
+ function keyToArray(key) {
16
+ if (!key) return [];
17
+ if (typeof key != "string") return key;
18
+ return key.split('.');
19
+ };
20
+
21
+ function locale() {
22
+ return I18n.locale || I18n.defaultLocale;
23
+ };
24
+
25
+ I18n.isValidNode = function(obj, node) {
26
+ // local undefined variable in case another library corrupts window.undefined
27
+ var undef;
28
+ return obj[node] !== null && obj[node] !== undef;
29
+ };
30
+
31
+ // Merge serveral hash options, checking if value is set before
32
+ // overwriting any value. The precedence is from left to right.
33
+ //
34
+ // I18n.prepareOptions({name: "John Doe"}, {name: "Mary Doe", role: "user"});
35
+ // #=> {name: "John Doe", role: "user"}
36
+ I18n.prepareOptions = function() {
37
+ var options = {};
38
+ var opts;
39
+ var count = arguments.length;
40
+
41
+ for (var i = 0; i < count; i++) {
42
+ opts = arguments[i];
43
+
44
+ if (!opts) {
45
+ continue;
46
+ }
47
+
48
+ for (var key in opts) {
49
+ if (!this.isValidNode(options, key)) {
50
+ options[key] = opts[key];
44
51
  }
45
- };
52
+ }
53
+ }
46
54
 
47
- // Returns other when 0 given
48
- function pluralize(value, count) {
49
- if(count === undefined) return value;
50
- return count === 1 ? value.one : value.other;
51
- };
55
+ return options;
56
+ };
57
+
58
+ // Works mostly the same as the Ruby equivalent, except there are
59
+ // no symbols in JavaScript, so keys are always strings. The only time
60
+ // this makes a difference is when differentiating between keys and values
61
+ // in the defaultValue option. Strings starting with ":" will be considered
62
+ // to be keys and used for lookup, while other strings are returned as-is.
63
+ I18n.translate = function(key, opts) {
64
+ if (typeof key != "string") { // Bulk lookup
65
+ var a = [], i;
66
+ for (i=0; i<key.length; i++) {
67
+ a.push(this.translate(key[i], opts));
68
+ }
69
+ return a;
70
+ } else {
71
+ opts = opts || {};
72
+ opts.defaultValue = opts.defaultValue || null;
73
+ key = keyToArray(opts.scope).concat(keyToArray(key));
74
+ var value = this.lookup(locale(), key, opts.defaultValue);
75
+
76
+ // fall back to I18n.default_locale for missing translations
77
+ if (value == null && I18n.locale != I18n.default_locale) value = this.lookup(I18n.default_locale, key, opts.defaultLocale);
78
+
79
+ if (typeof value != "string" && value) value = this.pluralize(value, opts.count);
80
+ if (typeof value == "string") value = interpolate(value, opts);
81
+ if (value == null) value = this.missingTranslation(key)
82
+ return value;
83
+ }
84
+ };
52
85
 
53
- // Works mostly the same as the Ruby equivalent, except there are
54
- // no symbols in JavaScript, so keys are always strings. The only
55
- // time this makes a difference is when differentiating between
56
- // keys and values in the defaultValue option. Strings starting
57
- // with ":" will be considered to be keys and used for lookup,
58
- // while other strings are returned as-is.
59
- function translate(key, options) {
60
- if(typeof key != "string") {
61
- // Bulk lookup
62
- var a = [], i;
63
- for(i = 0; i < key.length; i++) {
64
- a.push(translate(key[i], options));
65
- }
66
- return a;
86
+ I18n.t = I18n.translate;
87
+
88
+ // Looks up a translation using an array of strings where the last
89
+ // is the key and any string before that define the scope. The current
90
+ // locale is always prepended and does not need to be provided. The second
91
+ // parameter is an array of strings used as defaults if the key can not be
92
+ // found. If a key starts with ":" it is used as a key for lookup.
93
+ // This method does not perform pluralization or interpolation.
94
+ I18n.lookup = function(locale, keys, defaults) {
95
+ var i = 0, value = this.translations[locale];
96
+ defaults = typeof defaults == "string" ? [defaults] : (defaults || []);
97
+ while (keys[i]) {
98
+ value = value && value[keys[i]];
99
+ i++;
100
+ }
101
+ if (value) {
102
+ return value;
103
+ } else {
104
+ if (defaults.length == 0) {
105
+ return null;
106
+ } else if (defaults[0].substr(0,1) == ':') {
107
+ return this.lookup(locale, keys.slice(0, keys.length - 1).concat(keyToArray(defaults[0].substr(1))), defaults.slice(1));
108
+ } else {
109
+ return defaults[0];
110
+ }
111
+ }
112
+ };
113
+
114
+ I18n.localize = function(scope, value) {
115
+ switch (scope) {
116
+ case "currency":
117
+ return this.toCurrency(value);
118
+ case "number":
119
+ scope = this.lookup(locale(), ["number", "format"]);
120
+ return this.toNumber(value, scope);
121
+ case "percentage":
122
+ return this.toPercentage(value);
123
+ default:
124
+ if (scope.match(/^(date|time)/)) {
125
+ return this.toTime(scope, value);
67
126
  } else {
68
- options = options || {};
69
- options.defaultValue = options.defaultValue || null;
70
- key = keyToArray(options.scope).concat(keyToArray(key));
71
- var value = lookup(key, options.defaultValue);
72
- if(typeof value !== "string" && value) {
73
- value = pluralize(value, options.count);
74
- }
75
- if(typeof value === "string") {
76
- value = interpolate(value, options);
77
- }
78
- return value;
127
+ return value.toString();
79
128
  }
80
129
  }
130
+ };
131
+
132
+ I18n.l = I18n.localize;
133
+
134
+ I18n.parseDate = function(d) {
135
+ var matches, date;
136
+ matches = d.toString().match(/(\d{4})-(\d{2})-(\d{2})(?:[ |T](\d{2}):(\d{2}):(\d{2}))?(Z)?/);
137
+
138
+ if (matches) {
139
+ // date/time strings: yyyy-mm-dd hh:mm:ss or yyyy-mm-dd or yyyy-mm-ddThh:mm:ssZ
140
+ for (var i = 1; i <= 6; i++) {
141
+ matches[i] = parseInt(matches[i], 10) || 0;
142
+ }
143
+
144
+ // month starts on 0
145
+ matches[2] -= 1;
146
+
147
+ if (matches[7]) {
148
+ date = new Date(Date.UTC(matches[1], matches[2], matches[3], matches[4], matches[5], matches[6]));
149
+ } else {
150
+ date = new Date(matches[1], matches[2], matches[3], matches[4], matches[5], matches[6]);
151
+ }
152
+ } else if (typeof(d) == "number") {
153
+ // UNIX timestamp
154
+ date = new Date();
155
+ date.setTime(d);
156
+ } else {
157
+ // an arbitrary javascript string
158
+ date = new Date();
159
+ date.setTime(Date.parse(d));
160
+ }
161
+
162
+ return date;
163
+ };
164
+
165
+ I18n.toTime = function(scope, d) {
166
+ var date = this.parseDate(d);
167
+ var format = this.lookup(locale(), keyToArray(scope));
168
+
169
+ if (date.toString().match(/invalid/i)) {
170
+ return date.toString();
171
+ }
172
+
173
+ if (!format) {
174
+ return date.toString();
175
+ }
176
+
177
+ return this.strftime(date, format);
178
+ };
179
+
180
+ I18n.strftime = function(date, format) {
181
+ var options = this.lookup(locale(), ["date"]);
182
+
183
+ if (!options) {
184
+ return date.toString();
185
+ }
81
186
 
82
- return {
83
- translate: translate,
84
- t: translate
187
+ // get meridian from ":time" i18n key
188
+ options.time = this.lookup(locale(), ["time"]);
189
+ if (!options.time || !options.time.am || !options.time.pm) {
190
+ options.time = { am: "AM", pm: "PM" };
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 > 11 ? "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
+ } else if (hour12 === 0) {
210
+ hour12 = 12;
211
+ }
212
+
213
+ var padding = function(n) {
214
+ var s = "0" + n.toString();
215
+ return s.substr(s.length - 2);
85
216
  };
217
+
218
+ var f = format;
219
+ f = f.replace("%a", options.abbr_day_names[weekDay]);
220
+ f = f.replace("%A", options.day_names[weekDay]);
221
+ f = f.replace("%b", options.abbr_month_names[month]);
222
+ f = f.replace("%B", options.month_names[month]);
223
+ f = f.replace("%d", padding(day));
224
+ f = f.replace("%-d", day);
225
+ f = f.replace("%H", padding(hour));
226
+ f = f.replace("%-H", hour);
227
+ f = f.replace("%I", padding(hour12));
228
+ f = f.replace("%-I", hour12);
229
+ f = f.replace("%m", padding(month));
230
+ f = f.replace("%-m", month);
231
+ f = f.replace("%M", padding(mins));
232
+ f = f.replace("%-M", mins);
233
+ f = f.replace("%p", options.time[meridian]);
234
+ f = f.replace("%S", padding(secs));
235
+ f = f.replace("%-S", secs);
236
+ f = f.replace("%w", weekDay);
237
+ f = f.replace("%y", padding(year));
238
+ f = f.replace("%-y", padding(year).replace(/^0+/, ""));
239
+ f = f.replace("%Y", year);
240
+ f = f.replace("%z", timezoneoffset);
241
+
242
+ return f;
243
+ };
244
+
245
+ I18n.toNumber = function(number, options) {
246
+ options = this.prepareOptions(
247
+ options,
248
+ this.lookup(locale(), ["number", "format"]),
249
+ {precision: 3, separator: ".", delimiter: ",", strip_insignificant_zeros: false}
250
+ );
251
+
252
+ var negative = number < 0;
253
+ var string = Math.abs(number).toFixed(options.precision).toString();
254
+ var parts = string.split(".");
255
+
256
+ number = parts[0];
257
+ var precision = parts[1];
258
+
259
+ var n = [];
260
+
261
+ while (number.length > 0) {
262
+ n.unshift(number.substr(Math.max(0, number.length - 3), 3));
263
+ number = number.substr(0, number.length -3);
264
+ }
265
+
266
+ var formattedNumber = n.join(options.delimiter);
267
+
268
+ if (options.precision > 0) {
269
+ formattedNumber += options.separator + parts[1];
270
+ }
271
+
272
+ if (negative) {
273
+ formattedNumber = "-" + formattedNumber;
274
+ }
275
+
276
+ if (options.strip_insignificant_zeros) {
277
+ var regex = {
278
+ separator: new RegExp(options.separator.replace(/\./, "\\.") + "$")
279
+ , zeros: /0+$/
280
+ };
281
+
282
+ formattedNumber = formattedNumber
283
+ .replace(regex.zeros, "")
284
+ .replace(regex.separator, "");
285
+ }
286
+
287
+ return formattedNumber;
288
+ };
289
+
290
+ I18n.toCurrency = function(number, options) {
291
+ options = this.prepareOptions(
292
+ options,
293
+ this.lookup(locale(), ["number", "currency", "format"]),
294
+ this.lookup(locale(), ["number", "format"]),
295
+ {unit: "$", precision: 2, format: "%u%n", delimiter: ",", separator: "."}
296
+ );
297
+
298
+ number = this.toNumber(number, options);
299
+ number = options.format
300
+ .replace("%u", options.unit)
301
+ .replace("%n", number);
302
+
303
+ return number;
304
+ };
305
+
306
+ I18n.toHumanSize = function(number, options) {
307
+ var kb = 1024
308
+ , size = number
309
+ , iterations = 0
310
+ , unit
311
+ , precision
312
+ ;
313
+
314
+ while (size >= kb && iterations < 4) {
315
+ size = size / kb;
316
+ iterations += 1;
317
+ }
318
+
319
+ if (iterations === 0) {
320
+ unit = this.t("number.human.storage_units.units.byte", {count: size});
321
+ precision = 0;
322
+ } else {
323
+ unit = this.t("number.human.storage_units.units." + [null, "kb", "mb", "gb", "tb"][iterations]);
324
+ precision = (size - Math.floor(size) === 0) ? 0 : 1;
325
+ }
326
+
327
+ options = this.prepareOptions(
328
+ options,
329
+ {precision: precision, format: "%n%u", delimiter: ""}
330
+ );
331
+
332
+ number = this.toNumber(size, options);
333
+ number = options.format
334
+ .replace("%u", unit)
335
+ .replace("%n", number);
336
+
337
+ return number;
338
+ };
339
+
340
+ I18n.toPercentage = function(number, options) {
341
+ options = this.prepareOptions(
342
+ options,
343
+ this.lookup(locale(), ["number", "percentage", "format"]),
344
+ this.lookup(locale(), ["number", "format"]),
345
+ {precision: 3, separator: ".", delimiter: ""}
346
+ );
347
+
348
+ number = this.toNumber(number, options);
349
+ return number + "%";
350
+ };
351
+
352
+ I18n.pluralize = function(value, count) {
353
+ if (typeof count != 'number') return value;
354
+ return count == 1 ? value.one : value.other;
355
+ };
356
+
357
+ // Returns '[missing translation: "en.missing"]' when translation is not found
358
+ I18n.missingTranslation = function(key) {
359
+ var message = '[missing translation: "' + locale();
360
+ for (var i in key) {
361
+ message += "." + key[i];
362
+ }
363
+ return message += '"]';
364
+ };
365
+
86
366
  })();
87
367
 
88
368
  var t = (function(key, options) {
89
369
  return I18n.t(key, options);
90
370
  });
371
+
372
+ var l = (function(scope, value) {
373
+ return I18n.l(scope, value);
374
+ });
@@ -6,11 +6,10 @@ class GlobalizeTranslationsController < ApplicationController
6
6
  layout 'exvo_globalize'
7
7
 
8
8
  respond_to :html, :json, :js
9
-
9
+
10
10
  def show
11
11
  @translations = if params[:id].present?
12
- hash = I18n.backend.available_translations
13
- hash.has_key?(params[:id].to_sym) ? hash[params[:id].to_sym] : {}
12
+ I18n.backend.available_translations_scoped_by_locale_with_default(params[:id].to_sym)
14
13
  else
15
14
  I18n.backend.available_app_translations.merge({ :default_locale => I18n.default_locale })
16
15
  end
@@ -52,7 +51,7 @@ class GlobalizeTranslationsController < ApplicationController
52
51
  end
53
52
 
54
53
  private
55
-
54
+
56
55
  def globalize_translations_authenticator
57
56
  # get :show as JSON or JS does not require authentication
58
57
  return if params[:action].to_s == 'show' and (request.format.json? or request.format.js?)
@@ -4,13 +4,24 @@ module I18n
4
4
  module Implementation
5
5
 
6
6
  # Extend the Chain backend with a custom `available_translations` method
7
- # returning a combined hash with translations from all chained backends
7
+ # returning a combined Hash with translations from all chained backends
8
8
  def available_translations
9
9
  # reverse, so that the translations from the first backend (GlobalizeStore) overwrite/overshadow others
10
10
  @available_translations ||= backends.map { |backend| backend.available_translations }.reverse.inject(&:deep_merge)
11
11
  end
12
12
 
13
- # Return a hash only with Application translations
13
+ # Returns a Hash of translations for the specified locale merged with translations for the default_locale
14
+ def available_translations_scoped_by_locale_with_default(locale)
15
+ default_locale_translations = { I18n.default_locale => available_translations.fetch(I18n.default_locale) { {} } }
16
+
17
+ if locale != I18n.default_locale
18
+ default_locale_translations.deep_merge({ locale => available_translations.fetch(locale) { {} } })
19
+ else
20
+ default_locale_translations
21
+ end
22
+ end
23
+
24
+ # Return a Hash only with Application translations
14
25
  def available_app_translations
15
26
  # save original load_path
16
27
  load_path = I18n.load_path
@@ -1,3 +1,3 @@
1
1
  module ExvoGlobalize
2
- VERSION = "0.4.0"
2
+ VERSION = "0.5.0"
3
3
  end
@@ -20,7 +20,7 @@ describe GlobalizeTranslationsController do
20
20
  end
21
21
 
22
22
  it "returns a hash with translations" do
23
- assigns(:translations)[:name].should eq("YAML Name")
23
+ assigns(:translations)[:en][:name].should eq("YAML Name")
24
24
  end
25
25
  end
26
26
 
@@ -81,4 +81,10 @@ describe ExvoGlobalize do
81
81
  I18n.backend.available_translations[:en].has_key?(:title).should be_true
82
82
  end
83
83
 
84
+ it "lists all translations for a given locale including translations for the default locale" do
85
+ translations = I18n.backend.available_translations_scoped_by_locale_with_default(:pl)
86
+ translations[:en].has_key?(:title).should be_true
87
+ translations[:pl].has_key?(:mail).should be_true
88
+ end
89
+
84
90
  end
@@ -1,7 +1,384 @@
1
- describe("Translation", function() {
1
+ beforeEach(function() {
2
+ I18n.locale = "en"
3
+ })
4
+
5
+ describe("Basic translation functionality", function() {
6
+
7
+ it("translates the phrase", function() {
8
+ expect(I18n.translate("world")).toEqual("World")
9
+ })
10
+
11
+ it("translates the phrase for the 'pl' locale", function() {
12
+ I18n.locale = "pl"
13
+ expect(I18n.translate("world")).toEqual("Świat")
14
+ })
15
+
16
+ it("translates the phrase using I18n.t()", function() {
17
+ expect(I18n.t("world")).toEqual("World")
18
+ })
19
+
20
+ it("returns missing translation message when translation is not found", function() {
21
+ expect(I18n.t("missing")).toMatch(/missing translation/)
22
+ })
23
+
24
+ it("translates the phrase using scoped key with dots as separators", function() {
25
+ expect(I18n.t("page.intro.heading")).toEqual("Heading")
26
+ })
27
+
28
+ it("translates the phrase using dotted string scope param", function() {
29
+ expect(I18n.t("heading", {scope: "page.intro"})).toEqual("Heading")
30
+ })
31
+
32
+ it("translates the phrase using Array scope param", function() {
33
+ expect(I18n.t("heading", {scope: ["page", "intro"]})).toEqual("Heading")
34
+ })
35
+
36
+ it("supports interpolation during translation", function() {
37
+ expect(I18n.t("hello", {name: "John"})).toEqual("Hello John!")
38
+ })
39
+
40
+ })
41
+
42
+ describe("Global short functions", function() {
43
+
44
+ it("translates the phrase using t() global function", function() {
45
+ expect(t("world")).toEqual("World")
46
+ })
47
+
48
+ it("localizes the number using l() global function", function() {
49
+ expect(l("number", 1)).toEqual("1.000")
50
+ })
51
+
52
+ })
53
+
54
+ describe("Default value fallback", function() {
55
+
56
+ it("ignores defaultValue if translation is found", function() {
57
+ expect(I18n.t("world", {defaultValue: "New World"})).toEqual("World")
58
+ })
59
+
60
+ it("returns defaultValue if translation is not found", function() {
61
+ expect(I18n.t("non-existing-key", {defaultValue: "New World"})).toEqual("New World")
62
+ })
63
+
64
+ it("uses defaultValue to do another lookup if it starts with ':'", function() {
65
+ expect(I18n.t("non-existing-key", {defaultValue: ":world"})).toEqual("World")
66
+ })
67
+
68
+ it("allows an Array as defaultValue and keeps checking each of its keys for translation", function() {
69
+ expect(I18n.t("non-existing-key", {defaultValue: [":missing", ":world"]})).toEqual("World")
70
+ })
71
+
72
+ it("applies the same scope to the defaultValue as it does to the key", function() {
73
+ expect(I18n.t("non-existing-key", {scope: "page", defaultValue: [":title"]})).toEqual("Title")
74
+ })
75
+
76
+ })
77
+
78
+ describe("Locale fallback", function() {
79
+
80
+ it("falls back to I18n.default_locale when there is no translation available for the requested locale", function() {
81
+ I18n.locale = "pl"
82
+ expect(I18n.t("title", {scope: "page"})).toEqual("Title")
83
+ })
84
+
85
+ })
86
+
87
+ describe("Localization of numbers", function() {
88
+
89
+ beforeEach(function() {
90
+ I18n.locale = "pl"
91
+ })
92
+
93
+ it("localizes numbers with default settings", function() {
94
+ expect(I18n.toNumber(1)).toEqual("1,000")
95
+ expect(I18n.toNumber(1234567)).toEqual("1 234 567,000")
96
+ expect(I18n.toNumber(-1)).toEqual("-1,000")
97
+ expect(I18n.toNumber(-1234567)).toEqual("-1 234 567,000")
98
+ })
99
+
100
+ it("localizes numbers with custom options", function() {
101
+ expect(I18n.toNumber(1234.19, {precision: 0})).toEqual("1 234")
102
+ expect(I18n.toNumber(1234, {separator: '-'})).toEqual("1 234-000")
103
+ expect(I18n.toNumber(1234, {delimiter: '-'})).toEqual("1-234,000")
104
+ expect(I18n.toNumber(65, {precision: 4, strip_insignificant_zeros: true})).toEqual("65")
105
+ expect(I18n.toNumber(1.2, {precision: 4, strip_insignificant_zeros: true})).toEqual("1,2")
106
+ })
107
+
108
+ it("localizes numbers using localize() function", function() {
109
+ expect(I18n.l("number", 1)).toEqual("1,000")
110
+ expect(I18n.l("number", 1234567)).toEqual("1 234 567,000")
111
+ expect(I18n.l("number", -1)).toEqual("-1,000")
112
+ expect(I18n.l("number", -1234567)).toEqual("-1 234 567,000")
113
+ })
114
+ })
115
+
116
+ describe("Localization of percentages", function() {
117
+
118
+ beforeEach(function() {
119
+ I18n.locale = "pl"
120
+ })
121
+
122
+ it("localizes percentages with default settings", function() {
123
+ expect(I18n.toPercentage(1234)).toEqual("1234,00%")
124
+ })
125
+
126
+ it("localizes percentages with custom options", function() {
127
+ expect(I18n.toPercentage(1234.19, {delimiter: "_", precision: 0})).toEqual("1_234%")
128
+ })
129
+
130
+ it("localizes percentages using localize() function", function() {
131
+ expect(I18n.l("percentage", 12)).toEqual("12,00%")
132
+ })
133
+
134
+ })
135
+
136
+ describe("Localization of currencies", function() {
137
+
138
+ beforeEach(function() {
139
+ I18n.locale = "pl"
140
+ })
141
+
142
+ it("localizes currencies with default settings", function() {
143
+ expect(I18n.toCurrency(100.99)).toEqual("PLN 100,99")
144
+ expect(I18n.toCurrency(1000.99)).toEqual("PLN 1 000,99")
145
+ })
146
+
147
+ it("localizes currencies with custom options", function() {
148
+ expect(I18n.toCurrency(1234.19, {precision: 0})).toEqual("PLN 1 234")
149
+ expect(I18n.toCurrency(1234, {unit: "zł"})).toEqual("zł 1 234")
150
+ expect(I18n.toCurrency(1234, {separator: "-", strip_insignificant_zeros: false})).toEqual("PLN 1 234-00")
151
+ expect(I18n.toCurrency(1234, {delimiter: ""})).toEqual("PLN 1234")
152
+ expect(I18n.toCurrency(1234, {format: "%n %u"})).toEqual("1 234 PLN")
153
+ })
154
+
155
+ it("localizes currencies using localize() function", function() {
156
+ expect(I18n.l("currency", 12)).toEqual("PLN 12")
157
+ })
158
+
159
+ })
160
+
161
+ describe("Localization of human sizes", function() {
162
+
163
+ beforeEach(function() {
164
+ I18n.locale = "pl"
165
+ })
166
+
167
+ it("localizes numbers in human sizes", function() {
168
+ kb = 1024
169
+
170
+ expect(I18n.toHumanSize(1)).toEqual("1bajt")
171
+ expect(I18n.toHumanSize(102)).toEqual("102bajty")
172
+
173
+ expect(I18n.toHumanSize(kb)).toEqual("1KB")
174
+ expect(I18n.toHumanSize(kb * 1.5)).toEqual("1,5KB")
175
+
176
+ expect(I18n.toHumanSize(kb * kb)).toEqual("1MB")
177
+ expect(I18n.toHumanSize(kb * kb * 1.5)).toEqual("1,5MB")
178
+
179
+ expect(I18n.toHumanSize(kb * kb * kb)).toEqual("1GB")
180
+ expect(I18n.toHumanSize(kb * kb * kb * 1.5)).toEqual("1,5GB")
181
+
182
+ expect(I18n.toHumanSize(kb * kb * kb * kb)).toEqual("1TB")
183
+ expect(I18n.toHumanSize(kb * kb * kb * kb * 1.5)).toEqual("1,5TB")
184
+
185
+ expect(I18n.toHumanSize(kb * kb * kb * kb * kb)).toEqual("1024TB")
186
+ })
187
+
188
+ it("localizes numbers in human sizes using custom options", function() {
189
+ expect(I18n.toHumanSize(1024 * 1.6, {precision: 0})).toEqual("2KB")
190
+ })
191
+
192
+ })
193
+
194
+ describe("Date parsing", function() {
195
+
196
+ beforeEach(function() {
197
+ I18n.locale = "pl"
198
+ })
199
+
200
+ it("parses dates", function() {
201
+ expected = new Date(2009, 0, 24, 0, 0, 0)
202
+ actual = I18n.parseDate("2009-01-24")
203
+ expect(actual.toString()).toEqual(expected.toString())
204
+
205
+ expected = new Date(2009, 0, 24, 0, 15, 0)
206
+ actual = I18n.parseDate("2009-01-24 00:15:00")
207
+ expect(actual.toString()).toEqual(expected.toString())
208
+
209
+ expected = new Date(2009, 0, 24, 0, 0, 15)
210
+ actual = I18n.parseDate("2009-01-24 00:00:15")
211
+ expect(actual.toString()).toEqual(expected.toString())
212
+
213
+ expected = new Date(2009, 0, 24, 15, 33, 44)
214
+ actual = I18n.parseDate("2009-01-24 15:33:44")
215
+ expect(actual.toString()).toEqual(expected.toString())
216
+
217
+ expected = new Date(2009, 0, 24, 0, 0, 0)
218
+ actual = I18n.parseDate(expected.getTime())
219
+ expect(actual.toString()).toEqual(expected.toString())
220
+
221
+ expected = new Date(2009, 0, 24, 0, 0, 0)
222
+ actual = I18n.parseDate("01/24/2009")
223
+ expect(actual.toString()).toEqual(expected.toString())
224
+
225
+ expected = new Date(2009, 0, 24, 14, 33, 55)
226
+ actual = I18n.parseDate(expected).toString()
227
+ expect(actual).toEqual(expected.toString())
228
+
229
+ expected = new Date(2009, 0, 24, 15, 33, 44)
230
+ actual = I18n.parseDate("2009-01-24T15:33:44")
231
+ expect(actual.toString()).toEqual(expected.toString())
232
+
233
+ expected = new Date(Date.UTC(2009, 0, 24, 15, 33, 44))
234
+ actual = I18n.parseDate("2009-01-24T15:33:44Z")
235
+ expect(actual.toString()).toEqual(expected.toString())
236
+ })
237
+
238
+ it("formats dates", function() {
239
+ // 2009-04-26 19:35:44 (Sunday)
240
+ var date = new Date(2009, 3, 26, 19, 35, 44)
241
+
242
+ // short week day
243
+ expect(I18n.strftime(date, "%a")).toEqual("nie")
244
+
245
+ // full week day
246
+ expect(I18n.strftime(date, "%A")).toEqual("niedziela")
247
+
248
+ // short month
249
+ expect(I18n.strftime(date, "%b")).toEqual("kwi")
250
+
251
+ // full month
252
+ expect(I18n.strftime(date, "%B")).toEqual("kwiecień")
253
+
254
+ // day
255
+ expect(I18n.strftime(date, "%d")).toEqual("26")
256
+
257
+ // 24-hour
258
+ expect(I18n.strftime(date, "%H")).toEqual("19")
259
+
260
+ // 12-hour
261
+ expect(I18n.strftime(date, "%I")).toEqual("07")
262
+
263
+ // month
264
+ expect(I18n.strftime(date, "%m")).toEqual("04")
265
+
266
+ // minutes
267
+ expect(I18n.strftime(date, "%M")).toEqual("35")
268
+
269
+ // meridian
270
+ expect(I18n.strftime(date, "%p")).toEqual("po południu")
271
+
272
+ // seconds
273
+ expect(I18n.strftime(date, "%S")).toEqual("44")
274
+
275
+ // week day
276
+ expect(I18n.strftime(date, "%w")).toEqual("0")
277
+
278
+ // short year
279
+ expect(I18n.strftime(date, "%y")).toEqual("09")
280
+
281
+ // full year
282
+ expect(I18n.strftime(date, "%Y")).toEqual("2009")
283
+
284
+ // full date
285
+ expect(I18n.strftime(date, "%d. %B %Y, godzina %H:%M")).toEqual("26. kwiecień 2009, godzina 19:35")
286
+ })
287
+
288
+ it("formats dates without padding", function() {
289
+ // 2009-04-26 19:35:44 (Sunday)
290
+ var date = new Date(2009, 3, 9, 7, 8, 9)
291
+
292
+ // 24-hour without padding
293
+ expect(I18n.strftime(date, "%-H")).toEqual("7")
294
+
295
+ // 12-hour without padding
296
+ expect(I18n.strftime(date, "%-I")).toEqual("7")
297
+
298
+ // minutes without padding
299
+ expect(I18n.strftime(date, "%-M")).toEqual("8")
300
+
301
+ // seconds without padding
302
+ expect(I18n.strftime(date, "%-S")).toEqual("9")
303
+
304
+ // short year without padding
305
+ expect(I18n.strftime(date, "%-y")).toEqual("9")
306
+
307
+ // month without padding
308
+ expect(I18n.strftime(date, "%-m")).toEqual("4")
309
+
310
+ // day without padding
311
+ expect(I18n.strftime(date, "%-d")).toEqual("9")
312
+ })
313
+
314
+ it("formats dates with padding", function() {
315
+ // 2009-04-26 19:35:44 (Sunday)
316
+ var date = new Date(2009, 3, 9, 7, 8, 9)
317
+
318
+ // 24-hour
319
+ expect(I18n.strftime(date, "%H")).toEqual("07")
320
+
321
+ // 12-hour
322
+ expect(I18n.strftime(date, "%I")).toEqual("07")
323
+
324
+ // minutes
325
+ expect(I18n.strftime(date, "%M")).toEqual("08")
326
+
327
+ // seconds
328
+ expect(I18n.strftime(date, "%S")).toEqual("09")
329
+
330
+ // short year
331
+ expect(I18n.strftime(date, "%y")).toEqual("09")
332
+
333
+ // month
334
+ expect(I18n.strftime(date, "%m")).toEqual("04")
335
+
336
+ // day
337
+ expect(I18n.strftime(date, "%d")).toEqual("09")
338
+ })
339
+
340
+ it("formats dates with negative time zone", function() {
341
+ var date = new Date(2009, 3, 26, 19, 35, 44)
342
+ spyOn(date, "getTimezoneOffset").andReturn(345)
343
+
344
+ expect(I18n.strftime(date, "%z")).toMatch(/^(\+|-)[\d]{4}$/)
345
+ expect(I18n.strftime(date, "%z")).toEqual("-0545")
346
+ })
347
+
348
+ it("formats dates with positive time zone", function() {
349
+ var date = new Date(2009, 3, 26, 19, 35, 44)
350
+ spyOn(date, "getTimezoneOffset").andReturn(-345)
351
+
352
+ expect(I18n.strftime(date, "%z")).toMatch(/^(\+|-)[\d]{4}$/)
353
+ expect(I18n.strftime(date, "%z")).toEqual("+0545")
354
+ })
355
+
356
+
357
+ it("formats dates using hour12 values", function() {
358
+ var date = new Date(2009, 3, 26, 19, 35, 44)
359
+ expect(I18n.strftime(date, "%I")).toEqual("07")
360
+
361
+ date = new Date(2009, 3, 26, 12, 35, 44)
362
+ expect(I18n.strftime(date, "%I")).toEqual("12")
363
+
364
+ date = new Date(2009, 3, 26, 0, 35, 44)
365
+ expect(I18n.strftime(date, "%I")).toEqual("12")
366
+ })
367
+
368
+ it("parses and formats a Ruby Time.now.to_s string ", function() {
369
+ expect(I18n.strftime(I18n.parseDate("Wed Sep 14 12:03:11 +0200 2011"), "%Y/%m/%d %H:%M")).toEqual("2011/09/14 12:03")
370
+ })
371
+
372
+ it("localizes date strings", function() {
373
+ expect(I18n.l("date.formats.default", "2009-11-29")).toEqual("29-11-2009")
374
+ expect(I18n.l("date.formats.short", "2009-01-07")).toEqual("07 sty")
375
+ expect(I18n.l("date.formats.long", "2009-01-07")).toEqual("styczeń 07, 2009")
376
+ })
2
377
 
3
- it("should translate the phrase using the t() function", function() {
4
- expect(t("new")).toEqual("New")
378
+ it("localizes time strings", function() {
379
+ expect(I18n.l("time.formats.default", "2009-11-29 15:07:59")).toEqual("nie, 29 lis 2009 15:07:59 +0100")
380
+ expect(I18n.l("time.formats.short", "2009-01-07 09:12:35")).toEqual("07 sty 09:12")
381
+ expect(I18n.l("time.formats.long", "2009-11-29 15:07:59")).toEqual("listopad 29, 2009 15:07")
5
382
  })
6
383
 
7
384
  })
@@ -1,2 +1,98 @@
1
1
  I18n.locale = 'en'
2
- I18n.translations = { "new": "New" }
2
+ I18n.default_locale = 'en'
3
+
4
+ I18n.translations = {
5
+ en: {
6
+
7
+ hello: "Hello %{name}!",
8
+ world: "World",
9
+
10
+ page: {
11
+ footer: "Footer",
12
+ title: "Title",
13
+
14
+ intro: {
15
+ heading: "Heading"
16
+ }
17
+ }
18
+ },
19
+
20
+ pl: {
21
+ date: {
22
+ formats: {
23
+ "default": "%d-%m-%Y",
24
+ "short": "%d %b",
25
+ "long": "%B %d, %Y"
26
+ },
27
+ day_names: ["niedziela", "poniedziałek", "wtorek", "środa", "czwartek", "piątek", "sobota"],
28
+ abbr_day_names: ["nie", "pon", "wto", "śro", "czw", "pia", "sob"],
29
+ month_names: [null, "styczeń", "luty", "marzec", "kwiecień", "maj", "czerwiec", "lipiec", "sierpień", "wrzesień", "październik", "listopad", "grudzień"],
30
+ abbr_month_names: [null, "sty", "lut", "mar", "kwi", "maj", "cze", "lip", "sie", "wrz", "paź", "lis", "gru"],
31
+ order: ["day", "month", "year"]
32
+ },
33
+ time: {
34
+ formats: {
35
+ "default": "%a, %d %b %Y %H:%M:%S %z",
36
+ "short": "%d %b %H:%M",
37
+ "long": "%B %d, %Y %H:%M"
38
+ },
39
+ am: "przed południem",
40
+ pm: "po południu"
41
+ },
42
+ number: {
43
+ format: {
44
+ separator: ",",
45
+ delimiter: " ",
46
+ precision: 3,
47
+ significant: false,
48
+ strip_insignificant_zeros: false
49
+ },
50
+ currency: {
51
+ format: {
52
+ format: "%u %n",
53
+ unit: "PLN",
54
+ separator: ",",
55
+ delimiter: " ",
56
+ precision: 2,
57
+ significant: false,
58
+ strip_insignificant_zeros: true
59
+ }
60
+ },
61
+ percentage: {
62
+ format: {
63
+ delimiter: "",
64
+ separator: ",",
65
+ precision: 2
66
+ }
67
+ },
68
+ precision: {
69
+ format: {
70
+ delimiter: ""
71
+ }
72
+ },
73
+ human: {
74
+ format: {
75
+ delimiter: "",
76
+ precision: 3,
77
+ significant: true,
78
+ strip_insignificant_zeros: true
79
+ },
80
+ storage_units: {
81
+ format: "%n %u",
82
+ units: {
83
+ byte: {
84
+ one: "bajt",
85
+ other: "bajty"
86
+ },
87
+ kb: "KB",
88
+ mb: "MB",
89
+ gb: "GB",
90
+ tb: "TB"
91
+ }
92
+ }
93
+ }
94
+ },
95
+
96
+ world: "Świat"
97
+ }
98
+ }
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: exvo_globalize
3
3
  version: !ruby/object:Gem::Version
4
- hash: 15
4
+ hash: 11
5
5
  prerelease:
6
6
  segments:
7
7
  - 0
8
- - 4
8
+ - 5
9
9
  - 0
10
- version: 0.4.0
10
+ version: 0.5.0
11
11
  platform: ruby
12
12
  authors:
13
13
  - "Pawe\xC5\x82 Go\xC5\x9Bcicki"
@@ -15,7 +15,7 @@ autorequire:
15
15
  bindir: bin
16
16
  cert_chain: []
17
17
 
18
- date: 2011-09-09 00:00:00 +02:00
18
+ date: 2011-09-14 00:00:00 +02:00
19
19
  default_executable:
20
20
  dependencies:
21
21
  - !ruby/object:Gem::Dependency
@@ -389,7 +389,6 @@ files:
389
389
  - lib/generators/exvo_globalize/install_generator.rb
390
390
  - lib/generators/exvo_globalize/templates/migration.rb
391
391
  - lib/tasks/dump.rake
392
- - lib/tasks/jasmine.rake
393
392
  - spec/app.rb
394
393
  - spec/controllers/globalize_translations_controller_spec.rb
395
394
  - spec/exvo_globalize/caching_spec.rb
@@ -1,8 +0,0 @@
1
- begin
2
- require 'jasmine'
3
- load 'jasmine/tasks/jasmine.rake'
4
- rescue LoadError
5
- task :jasmine do
6
- abort "Jasmine is not available. In order to run jasmine, you must: (sudo) gem install jasmine"
7
- end
8
- end