exvo_globalize 0.4.0 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
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