i18n-js 3.0.0.rc14 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 10a07ab318e610dfc7102d3d88015d8a55c42a76
4
- data.tar.gz: 21fa8daa428b711f6d592c3b14bad7183e5af2f8
3
+ metadata.gz: 353417f345acd62bc99d23f7a5fec4009cc0ee3e
4
+ data.tar.gz: f06f7d88940146630ad37d71dc64d0ed7e746a75
5
5
  SHA512:
6
- metadata.gz: cef3f6f68e7ca327e96902093069c47df6897074d35db121700076e3e7137d1ae68a9222e76ded1bea5e8d3145597cc0f37c9651d12a3b62ccbf766ce8374186
7
- data.tar.gz: 6940d5acc297f6482cb74caa56f64c85b2be20065a069fd26267956ee157ce139b2f92b425c9e2e55ce983c285bd66dcdae861454bc4371aa4e478687613da4d
6
+ metadata.gz: 40cb6d125502a38e0a9e87bb57fb57f3a4913ad6647a0431bd9c81cfa40b60b37b77562bec24c93c0a3b40687be33163107c63e9af7abc7a2d35a323dfc8fcee
7
+ data.tar.gz: a6503dbaecb63aa9638f348e28625df2854399dd5c4cacacc776a13f2fdfd88aef8ca21f0aed3d6b6e1cb56571c83d8ca270ba6f949c4b14c452cdf177472fcd
data/.travis.yml CHANGED
@@ -5,18 +5,20 @@ language: ruby
5
5
  cache:
6
6
  - bundler
7
7
  rvm:
8
- - 2.0
9
- - 2.1
10
- - 2.2
8
+ - 2.1.10
9
+ - 2.2.5
11
10
  # Since the Travis Build Env does not recognize `2.3` alias yet
12
11
  # We need to specify the version precisely
13
- - 2.3.0
12
+ - 2.3.3
13
+ - 2.4.0
14
14
  - ruby-head
15
- before_install: # Need to install something extra to test JS
16
- - npm install jasmine-node@1.14.2
15
+ before_install:
16
+ # Need to install something extra to test JS
17
+ - npm install
17
18
  gemfile:
18
19
  - gemfiles/i18n_0_6.gemfile
19
20
  - gemfiles/i18n_0_7.gemfile
21
+ - gemfiles/i18n_0_8.gemfile
20
22
  matrix:
21
23
  fast_finish: true
22
24
  allow_failures:
data/Appraisals CHANGED
@@ -6,3 +6,7 @@ end
6
6
  appraise "i18n_0_7" do
7
7
  gem "i18n", "~> 0.7.0"
8
8
  end
9
+
10
+ appraise "i18n_0_8" do
11
+ gem "i18n", "~> 0.8.0"
12
+ end
data/CHANGELOG.md CHANGED
@@ -2,6 +2,7 @@
2
2
  All notable changes to this project will be documented in this file.
3
3
  This project adheres to [Semantic Versioning](http://semver.org/).
4
4
 
5
+
5
6
  ## [Unreleased]
6
7
 
7
8
  ### Added
@@ -17,6 +18,57 @@ This project adheres to [Semantic Versioning](http://semver.org/).
17
18
  - Nothing
18
19
 
19
20
 
21
+ ## [3.0.0] - 2017-04-01
22
+
23
+ This is a fake official release, the *real* one will be `3.0.0.rc17`
24
+ And today is not April Fools' Day
25
+
26
+ ### Fixed
27
+
28
+ - Ends the longest Release Candidate period among all ruby gems
29
+ (v3.0.0.rc1 released at 2012-05-10)
30
+
31
+
32
+ ## [3.0.0.rc16] - 2017-03-13
33
+
34
+ ### Changed
35
+
36
+ - [Ruby] Drop support for Ruby < `2.1.0`
37
+
38
+ ### Fixed
39
+
40
+ - [JS] Make defaultValue works on plural translation
41
+ - [JS] Fix UMD pattern so the global/root won’t be undefined
42
+
43
+
44
+ ## [3.0.0.rc15] - 2016-12-07
45
+
46
+ ### Added
47
+
48
+ - Nothing
49
+
50
+ ### Changed
51
+
52
+ - [JS] Allow `defaultValue` to work in pluralization
53
+ (PR: https://github.com/fnando/i18n-js/pull/433)
54
+ - [Ruby] Stop validating the fallback locales against `I18n.available_locales`
55
+ This allows some locales to be used as fallback locales, but not to be generated in JS.
56
+ (PR: https://github.com/fnando/i18n-js/pull/425)
57
+ - [Ruby] Remove dependency on gem `activesupport`
58
+
59
+ ### Fixed
60
+
61
+ - [JS] Stop converting numeric & boolean values into objects
62
+ when merging objects with `I18n.extend`
63
+ (PR: https://github.com/fnando/i18n-js/pull/420)
64
+ - [JS] Fix I18n pluralization fallback when tree is empty
65
+ (PR: https://github.com/fnando/i18n-js/pull/435)
66
+ - [Ruby] Use old syntax to define lambda for compatibility with older Rubies
67
+ (Issue: https://github.com/fnando/i18n-js/issues/419)
68
+ - [Ruby] Fix error raised in middleware cache cleaning in parallel test
69
+ (Issue: https://github.com/fnando/i18n-js/issues/436)
70
+
71
+
20
72
  ## [3.0.0.rc14] - 2016-08-29
21
73
 
22
74
  ### Changed
@@ -188,7 +240,10 @@ This project adheres to [Semantic Versioning](http://semver.org/).
188
240
 
189
241
 
190
242
 
191
- [Unreleased]: https://github.com/fnando/i18n-js/compare/v3.0.0.rc14...HEAD
243
+ [Unreleased]: https://github.com/fnando/i18n-js/compare/v3.0.0...HEAD
244
+ [3.0.0]: https://github.com/fnando/i18n-js/compare/v3.0.0.rc16...v3.0.0
245
+ [3.0.0.rc16]: https://github.com/fnando/i18n-js/compare/v3.0.0.rc15...v3.0.0.rc16
246
+ [3.0.0.rc15]: https://github.com/fnando/i18n-js/compare/v3.0.0.rc14...v3.0.0.rc15
192
247
  [3.0.0.rc14]: https://github.com/fnando/i18n-js/compare/v3.0.0.rc13...v3.0.0.rc14
193
248
  [3.0.0.rc13]: https://github.com/fnando/i18n-js/compare/v3.0.0.rc12...v3.0.0.rc13
194
249
  [3.0.0.rc12]: https://github.com/fnando/i18n-js/compare/v3.0.0.rc11...v3.0.0.rc12
data/README.md CHANGED
@@ -1,7 +1,12 @@
1
1
  # I18n.js
2
2
 
3
+ [![Gem Version](http://img.shields.io/gem/v/i18n-js.svg?style=flat-square)](http://badge.fury.io/rb/i18n-js)
4
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg?style=flat-square)](https://opensource.org/licenses/MIT)
5
+
3
6
  [![Build Status](http://img.shields.io/travis/fnando/i18n-js.svg?style=flat-square)](https://travis-ci.org/fnando/i18n-js)
7
+ [![Dependency Status](http://img.shields.io/gemnasium/fnando/i18n-js.svg?style=flat-square)](https://gemnasium.com/fnando/i18n-js)
4
8
  [![Code Climate](http://img.shields.io/codeclimate/github/fnando/i18n-js.svg?style=flat-square)](https://codeclimate.com/github/fnando/i18n-js)
9
+
5
10
  [![Gitter](https://img.shields.io/badge/gitter-join%20chat-1dce73.svg?style=flat-square)](https://gitter.im/fnando/i18n-js)
6
11
 
7
12
  It's a small library to provide the Rails I18n translations on the JavaScript.
@@ -27,11 +32,7 @@ The `master` branch (including this README) is for latest `3.0.0.rc` instead of
27
32
 
28
33
  Add the gem to your Gemfile.
29
34
  ```ruby
30
- source "https://rubygems.org"
31
- gem "rails", "your_rails_version"
32
- # You only need this RC version constraint during the development of `3.0.0`, once stable version is released you can remove `rc11` suffix
33
- # `3.0.0.rc11` is the latest version of released RC version when this entry is changed, you might want to change it later
34
- gem "i18n-js", ">= 3.0.0.rc11"
35
+ gem "i18n-js"
35
36
  ```
36
37
 
37
38
  #### Rails app with [Asset Pipeline](http://guides.rubyonrails.org/asset_pipeline.html)
@@ -603,6 +604,27 @@ The accepted formats are:
603
604
 
604
605
  Check out `spec/*.spec.js` files for more examples!
605
606
 
607
+ #### Using pluralization and number formatting together
608
+ Sometimes you might want to display translation with formatted number, like adding thousand delimiters to displayed number
609
+ You can do this:
610
+ ```json
611
+ {
612
+ "en": {
613
+ "point": {
614
+ "one": "1 Point",
615
+ "other": "{{formatted_number}} Points",
616
+ "zero": "0 Points"
617
+ }
618
+ }
619
+ }
620
+ ```
621
+ ```js
622
+ var point_in_number = 1000;
623
+ I18n.t('point', { count: point_in_number, formatted_number: I18n.toNumber(point_in_number) });
624
+ ```
625
+ Output should be `1,000 points`
626
+
627
+
606
628
  ## Using multiple exported translation files on a page.
607
629
  This method is useful for very large apps where a single contained translations.js file is not desirable. Examples would be a global translations file and a more specific route translation file.
608
630
 
@@ -742,6 +764,7 @@ I18n.translations["pt-BR"] = {
742
764
  }
743
765
  ```
744
766
 
767
+
745
768
  ## Known Issues
746
769
 
747
770
  ### Missing translations in precompiled file(s) after adding any new locale file
@@ -774,6 +797,16 @@ This means that new locale files will not be detected, and so they will not trig
774
797
 
775
798
  **Note:** See issue [#213](https://github.com/fnando/i18n-js/issues/213) for more details and discussion of this issue.
776
799
 
800
+ ### Translations in JS are not updated when Sprockets not loaded before this gem
801
+
802
+ The "rails engine" declaration will try to detect existence of "sprockets" before adding the initailizer
803
+ If sprockets is loaded after this gem, the preprocessor for
804
+ making JS translations file cache to depend on content of locale files will not be hooked.
805
+ So ensure sprockets is loaded before this gem like moving entry of sprockets in Gemfile or adding "require" statements for sprockets somewhere.
806
+
807
+ **Note:** See issue [#404](https://github.com/fnando/i18n-js/issues/404) for more details and discussion of this issue.
808
+
809
+
777
810
  ## Maintainer
778
811
 
779
812
  - Nando Vieira - <http://nandovieira.com.br>
@@ -1,17 +1,22 @@
1
1
  <%# encoding: utf-8 %>
2
2
 
3
- ;(function(factory) {
4
- if (typeof module !== 'undefined' && module.exports) {
5
- // Node/CommonJS
6
- factory(require('i18n'));
7
- } else if (typeof define === 'function' && define.amd) {
8
- // AMD
9
- define(['i18n'], factory);
3
+ // Using UMD pattern from
4
+ // https://github.com/umdjs/umd#regular-module
5
+ // `returnExports.js` version
6
+ ;(function (root, factory) {
7
+ if (typeof define === 'function' && define.amd) {
8
+ // AMD. Register as an anonymous module.
9
+ define(["i18n"], factory);
10
+ } else if (typeof module === 'object' && module.exports) {
11
+ // Node. Does not work with strict CommonJS, but
12
+ // only CommonJS-like environments that support module.exports,
13
+ // like Node.
14
+ factory(require("i18n"));
10
15
  } else {
11
- // Browser globals
12
- factory(this.I18n);
16
+ // Browser globals (root is window)
17
+ factory(root.I18n);
13
18
  }
14
- }(function(I18n) {
19
+ }(this, function(I18n) {
15
20
  "use strict";
16
21
 
17
22
  I18n.translations = <%= I18n::JS.filtered_translations.to_json %>;
@@ -12,19 +12,23 @@
12
12
  // See tests for specific formatting like numbers and dates.
13
13
  //
14
14
 
15
- ;(function(factory) {
16
- if (typeof module !== 'undefined' && module.exports) {
17
- // Node/CommonJS
18
- module.exports = factory(this);
19
- } else if (typeof define === 'function' && define.amd) {
20
- // AMD
21
- var global=this;
22
- define('i18n', function(){ return factory(global);});
15
+ // Using UMD pattern from
16
+ // https://github.com/umdjs/umd#regular-module
17
+ // `returnExports.js` version
18
+ ;(function (root, factory) {
19
+ if (typeof define === 'function' && define.amd) {
20
+ // AMD. Register as an anonymous module.
21
+ define("i18n", function(){ return factory(root);});
22
+ } else if (typeof module === 'object' && module.exports) {
23
+ // Node. Does not work with strict CommonJS, but
24
+ // only CommonJS-like environments that support module.exports,
25
+ // like Node.
26
+ module.exports = factory(root);
23
27
  } else {
24
- // Browser globals
25
- this.I18n = factory(this);
28
+ // Browser globals (root is window)
29
+ root.I18n = factory(root);
26
30
  }
27
- }(function(global) {
31
+ }(this, function(global) {
28
32
  "use strict";
29
33
 
30
34
  // Use previously defined object if exists in current scope
@@ -53,11 +57,23 @@
53
57
 
54
58
  // Is a given value an array?
55
59
  // Borrowed from Underscore.js
56
- var isArray = function(obj) {
60
+ var isArray = function(val) {
57
61
  if (Array.isArray) {
58
- return Array.isArray(obj);
62
+ return Array.isArray(val);
59
63
  };
60
- return Object.prototype.toString.call(obj) === '[object Array]';
64
+ return Object.prototype.toString.call(val) === '[object Array]';
65
+ };
66
+
67
+ var isString = function(val) {
68
+ return typeof value == 'string' || Object.prototype.toString.call(val) === '[object String]';
69
+ };
70
+
71
+ var isNumber = function(val) {
72
+ return typeof val == 'number' || Object.prototype.toString.call(val) === '[object Number]';
73
+ };
74
+
75
+ var isBoolean = function(val) {
76
+ return val === true || val === false;
61
77
  };
62
78
 
63
79
  var decimalAdjust = function(type, value, exp) {
@@ -83,7 +99,7 @@
83
99
  var key, value;
84
100
  for (key in obj) if (obj.hasOwnProperty(key)) {
85
101
  value = obj[key];
86
- if (Object.prototype.toString.call(value) === '[object String]') {
102
+ if (isString(value) || isNumber(value) || isBoolean(value)) {
87
103
  dest[key] = value;
88
104
  } else {
89
105
  if (dest[key] == null) dest[key] = {};
@@ -347,7 +363,6 @@
347
363
  if (!translations) {
348
364
  continue;
349
365
  }
350
-
351
366
  while (scopes.length) {
352
367
  translations = translations[scopes.shift()];
353
368
 
@@ -366,6 +381,75 @@
366
381
  }
367
382
  };
368
383
 
384
+ // lookup pluralization rule key into translations
385
+ I18n.pluralizationLookupWithoutFallback = function(count, locale, translations) {
386
+ var pluralizer = this.pluralization.get(locale)
387
+ , pluralizerKeys = pluralizer(count)
388
+ , pluralizerKey
389
+ , message;
390
+
391
+ if (isObject(translations)) {
392
+ while (pluralizerKeys.length) {
393
+ pluralizerKey = pluralizerKeys.shift();
394
+ if (this.isSet(translations[pluralizerKey])) {
395
+ message = translations[pluralizerKey];
396
+ break;
397
+ }
398
+ }
399
+ }
400
+
401
+ return message;
402
+ };
403
+
404
+ // Lookup dedicated to pluralization
405
+ I18n.pluralizationLookup = function(count, scope, options) {
406
+ options = this.prepareOptions(options);
407
+ var locales = this.locales.get(options.locale).slice()
408
+ , requestedLocale = locales[0]
409
+ , locale
410
+ , scopes
411
+ , translations
412
+ , message
413
+ ;
414
+ scope = this.getFullScope(scope, options);
415
+
416
+ while (locales.length) {
417
+ locale = locales.shift();
418
+ scopes = scope.split(this.defaultSeparator);
419
+ translations = this.translations[locale];
420
+
421
+ if (!translations) {
422
+ continue;
423
+ }
424
+
425
+ while (scopes.length) {
426
+ translations = translations[scopes.shift()];
427
+ if (!isObject(translations)) {
428
+ break;
429
+ }
430
+ if (scopes.length == 0) {
431
+ message = this.pluralizationLookupWithoutFallback(count, locale, translations);
432
+ }
433
+ }
434
+ if (message != null && message != undefined) {
435
+ break;
436
+ }
437
+ }
438
+
439
+ if (message == null || message == undefined) {
440
+ if (this.isSet(options.defaultValue)) {
441
+ if (isObject(options.defaultValue)) {
442
+ message = this.pluralizationLookupWithoutFallback(count, options.locale, options.defaultValue);
443
+ } else {
444
+ message = options.defaultValue;
445
+ }
446
+ translations = options.defaultValue;
447
+ }
448
+ }
449
+
450
+ return { message: message, translations: translations };
451
+ };
452
+
369
453
  // Rails changed the way the meridian is stored.
370
454
  // It started with `date.meridian` returning an array,
371
455
  // then it switched to `time.am` and `time.pm`.
@@ -445,6 +529,7 @@
445
529
  I18n.translate = function(scope, options) {
446
530
  options = this.prepareOptions(options);
447
531
 
532
+ var copiedOptions = this.prepareOptions(options);
448
533
  var translationOptions = this.createTranslationOptions(scope, options);
449
534
 
450
535
  var translation;
@@ -470,7 +555,7 @@
470
555
  if (typeof(translation) === "string") {
471
556
  translation = this.interpolate(translation, options);
472
557
  } else if (isObject(translation) && this.isSet(options.count)) {
473
- translation = this.pluralize(options.count, translation, options);
558
+ translation = this.pluralize(options.count, scope, copiedOptions);
474
559
  }
475
560
 
476
561
  return translation;
@@ -516,32 +601,22 @@
516
601
  // which will be retrieved from `options`.
517
602
  I18n.pluralize = function(count, scope, options) {
518
603
  options = this.prepareOptions(options);
519
- var translations, pluralizer, keys, key, message;
604
+ var pluralizer, message, result;
520
605
 
521
- if (isObject(scope)) {
522
- translations = scope;
523
- } else {
524
- translations = this.lookup(scope, options);
525
- }
526
-
527
- if (!translations) {
606
+ result = this.pluralizationLookup(count, scope, options);
607
+ if (result.translations == undefined || result.translations == null) {
528
608
  return this.missingTranslation(scope, options);
529
609
  }
530
610
 
531
- pluralizer = this.pluralization.get(options.locale);
532
- keys = pluralizer(count);
533
-
534
- while (keys.length) {
535
- key = keys.shift();
611
+ options.count = String(count);
536
612
 
537
- if (this.isSet(translations[key])) {
538
- message = translations[key];
539
- break;
540
- }
613
+ if (result.message != undefined && result.message != null) {
614
+ return this.interpolate(result.message, options);
615
+ }
616
+ else {
617
+ pluralizer = this.pluralization.get(options.locale);
618
+ return this.missingTranslation(scope + '.' + pluralizer(count)[0], options);
541
619
  }
542
-
543
- options.count = String(count);
544
- return this.interpolate(message, options);
545
620
  };
546
621
 
547
622
  // Return a missing translation message for the given parameters.
@@ -0,0 +1,7 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem "i18n", "~> 0.8.0"
6
+
7
+ gemspec :path => "../"
data/i18n-js.gemspec CHANGED
@@ -4,7 +4,7 @@ require "i18n/js/version"
4
4
 
5
5
  Gem::Specification.new do |s|
6
6
  s.name = "i18n-js"
7
- s.version = I18n::JS::Version::STRING
7
+ s.version = I18n::JS::VERSION
8
8
  s.platform = Gem::Platform::RUBY
9
9
  s.authors = ["Nando Vieira"]
10
10
  s.email = ["fnando.vieira@gmail.com"]
@@ -19,11 +19,11 @@ Gem::Specification.new do |s|
19
19
  s.require_paths = ["lib"]
20
20
 
21
21
  s.add_dependency "i18n", "~> 0.6", ">= 0.6.6"
22
+
22
23
  s.add_development_dependency "appraisal", "~> 2.0"
23
- s.add_development_dependency "activesupport", ">= 3.2.22"
24
24
  s.add_development_dependency "rspec", "~> 3.0"
25
- s.add_development_dependency "rake", "~> 10.0"
25
+ s.add_development_dependency "rake", "~> 12.0"
26
26
  s.add_development_dependency "gem-release", ">= 0.7"
27
27
 
28
- s.required_ruby_version = ">= 1.9.3"
28
+ s.required_ruby_version = ">= 2.1.0"
29
29
  end
@@ -32,8 +32,11 @@ module I18n
32
32
  #
33
33
  # For detail see Pull Request:
34
34
  # https://github.com/fnando/i18n-js/pull/371
35
+ #
36
+ # Not using -> for JRuby compatibility
37
+ # See https://github.com/fnando/i18n-js/issues/419
35
38
  initializer_args = case sprockets_version
36
- when -> (v) { v2_only.match?("", v) || v3_plus.match?("", v) }
39
+ when lambda {|v| v2_only.match?("", v) || v3_plus.match?("", v) }
37
40
  { after: :engines_blank_point, before: :finisher_hook }
38
41
  else
39
42
  raise StandardError, "Sprockets version #{sprockets_version} is not supported"
@@ -32,7 +32,6 @@ module I18n
32
32
  end
33
33
 
34
34
  locales.map! { |locale| locale.to_sym }
35
- ensure_valid_locales!(locales)
36
35
  locales
37
36
  end
38
37
 
@@ -66,16 +65,6 @@ module I18n
66
65
 
67
66
  fail ArgumentError, "If fallbacks is passed as Array, it must ony include Strings or Symbols. Given: #{fallbacks}"
68
67
  end
69
-
70
- # Ensures that only valid locales are returned.
71
- #
72
- # @note
73
- # This ignores option `I18n.enforce_available_locales`
74
- def ensure_valid_locales!(locales)
75
- if locales.any? { |locale| !::I18n.available_locales.include?(locale) }
76
- fail ArgumentError, "Valid locales: #{::I18n.available_locales.join(", ")} - Given Locales: #{locales.join(", ")}"
77
- end
78
- end
79
68
  end
80
69
  end
81
70
  end
@@ -1,3 +1,5 @@
1
+ require "fileutils"
2
+
1
3
  module I18n
2
4
  module JS
3
5
  class Middleware
@@ -32,7 +34,13 @@ module I18n
32
34
  end
33
35
 
34
36
  def clear_cache
35
- File.delete(cache_path) if File.exist?(cache_path)
37
+ # `File.delete` will raise error when "multiple worker"
38
+ # Are running at the same time, like in a parallel test
39
+ #
40
+ # `FileUtils.rm_f` is tested manually
41
+ #
42
+ # See https://github.com/fnando/i18n-js/issues/436
43
+ FileUtils.rm_f(cache_path) if File.exist?(cache_path)
36
44
  end
37
45
 
38
46
  def save_cache(new_cache)
@@ -0,0 +1,36 @@
1
+ module I18n
2
+ module JS
3
+ # @api private
4
+ module Private
5
+ # Hash with string keys converted to symbol keys
6
+ # Used for handling values read on YAML
7
+ #
8
+ # @api private
9
+ class HashWithSymbolKeys < ::Hash
10
+ # An instance can only be created by passing in another hash
11
+ def initialize(hash)
12
+ raise TypeError unless hash.is_a?(::Hash)
13
+
14
+ hash.each_key do |key|
15
+ # Objects like `Integer` does not have `to_sym`
16
+ new_key = key.respond_to?(:to_sym) ? key.to_sym : key
17
+ self[new_key] = hash[key]
18
+ end
19
+
20
+ self.default = hash.default if hash.default
21
+ self.default_proc = hash.default_proc if hash.default_proc
22
+
23
+ freeze
24
+ end
25
+
26
+ # From AS Core extension
27
+ def slice(*keys)
28
+ hash = keys.each_with_object(Hash.new) do |k, hash|
29
+ hash[k] = self[k] if has_key?(k)
30
+ end
31
+ self.class.new(hash)
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
@@ -1,3 +1,5 @@
1
+ require "i18n/js/private/hash_with_symbol_keys"
2
+
1
3
  module I18n
2
4
  module JS
3
5
 
@@ -10,7 +12,13 @@ module I18n
10
12
 
11
13
  def initialize(file, translations, options = {})
12
14
  @file = file
13
- @translations = translations
15
+ # `#slice` will be used
16
+ # But when activesupport is absent,
17
+ # the core extension from `i18n` gem will be used instead
18
+ # And it's causing errors (at least in test)
19
+ #
20
+ # So the input is wrapped by our class for better `#slice`
21
+ @translations = Private::HashWithSymbolKeys.new(translations)
14
22
  @namespace = options[:namespace] || 'I18n'
15
23
  @pretty_print = !!options[:pretty_print]
16
24
  @js_extend = options.key?(:js_extend) ? !!options[:js_extend] : true
@@ -1,10 +1,5 @@
1
1
  module I18n
2
2
  module JS
3
- module Version
4
- MAJOR = 3
5
- MINOR = 0
6
- PATCH = 0
7
- STRING = "#{MAJOR}.#{MINOR}.#{PATCH}.rc14"
8
- end
3
+ VERSION = "3.0.0"
9
4
  end
10
5
  end
data/lib/i18n/js.rb CHANGED
@@ -1,7 +1,9 @@
1
1
  require "yaml"
2
- require "i18n"
3
2
  require "fileutils"
3
+ require "i18n"
4
+
4
5
  require "i18n/js/utils"
6
+ require "i18n/js/private/hash_with_symbol_keys"
5
7
 
6
8
  module I18n
7
9
  module JS
@@ -43,16 +45,23 @@ module I18n
43
45
  end
44
46
 
45
47
  def self.configured_segments
46
- config[:translations].inject([]) do |segments, options|
47
- file = options[:file]
48
- only = options[:only] || '*'
49
- exceptions = [options[:except] || []].flatten
48
+ config[:translations].inject([]) do |segments, options_hash|
49
+ options_hash_with_symbol_keys = Private::HashWithSymbolKeys.new(options_hash)
50
+ file = options_hash_with_symbol_keys[:file]
51
+ only = options_hash_with_symbol_keys[:only] || '*'
52
+ exceptions = [options_hash_with_symbol_keys[:except] || []].flatten
50
53
 
51
54
  result = segment_for_scope(only, exceptions)
52
55
 
53
56
  merge_with_fallbacks!(result) if fallbacks
54
57
 
55
- segments << Segment.new(file, result, extract_segment_options(options)) unless result.empty?
58
+ unless result.empty?
59
+ segments << Segment.new(
60
+ file,
61
+ result,
62
+ extract_segment_options(options_hash_with_symbol_keys),
63
+ )
64
+ end
56
65
 
57
66
  segments
58
67
  end
@@ -91,11 +100,13 @@ module I18n
91
100
  # custom output directory
92
101
  def self.config
93
102
  if config?
94
- erb = ERB.new(File.read(config_file_path)).result
95
- (YAML.load(erb) || {}).with_indifferent_access
103
+ erb_result_from_yaml_file = ERB.new(File.read(config_file_path)).result
104
+ Private::HashWithSymbolKeys.new(
105
+ (::YAML.load(erb_result_from_yaml_file) || {})
106
+ )
96
107
  else
97
- {}
98
- end
108
+ Private::HashWithSymbolKeys.new({})
109
+ end.freeze
99
110
  end
100
111
 
101
112
  # Check if configuration file exist
@@ -151,7 +162,14 @@ module I18n
151
162
  def self.translations
152
163
  ::I18n.backend.instance_eval do
153
164
  init_translations unless initialized?
154
- translations.slice(*::I18n.available_locales)
165
+ # When activesupport is absent,
166
+ # the core extension (`#slice`) from `i18n` gem will be used instead
167
+ # And it's causing errors (at least in test)
168
+ #
169
+ # So the input is wrapped by our class for better `#slice`
170
+ Private::HashWithSymbolKeys.new(translations).
171
+ slice(*::I18n.available_locales).
172
+ to_h
155
173
  end
156
174
  end
157
175
 
@@ -184,7 +202,10 @@ module I18n
184
202
  end
185
203
 
186
204
  def self.extract_segment_options(options)
187
- segment_options = {js_extend: js_extend, sort_translation_keys: sort_translation_keys?}.with_indifferent_access
205
+ segment_options = Private::HashWithSymbolKeys.new({
206
+ js_extend: js_extend,
207
+ sort_translation_keys: sort_translation_keys?,
208
+ }).freeze
188
209
  segment_options.merge(options.slice(*Segment::OPTIONS))
189
210
  end
190
211
 
data/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "i18n-js",
3
3
  "version": "0.0.0",
4
4
  "devDependencies": {
5
- "jasmine-node": "*"
5
+ "jasmine-node": "^1.14.5"
6
6
  },
7
7
  "main": "app/assets/javascripts/i18n.js",
8
8
  "scripts": {
@@ -58,5 +58,28 @@ describe("Extend", function () {
58
58
  }
59
59
 
60
60
  expect(I18n.extend(obj1, obj2)).toEqual(expected);
61
- })
61
+ });
62
+
63
+ it("should correctly merge string, numberic and boolean values", function() {
64
+ var obj1 = {
65
+ test1: {
66
+ test2: false
67
+ }
68
+ }
69
+ , obj2 = {
70
+ test1: {
71
+ test3: 23,
72
+ test4: 'abc'
73
+ }
74
+ }
75
+ , expected = {
76
+ test1: {
77
+ test2: false
78
+ , test3: 23
79
+ , test4: 'abc'
80
+ }
81
+ }
82
+
83
+ expect(I18n.extend(obj1, obj2)).toEqual(expected);
84
+ });
62
85
  });
@@ -46,6 +46,17 @@ describe("Interpolation", function(){
46
46
  expect(I18n.t(translation_key, {count: 5})).toEqual("Hello World!");
47
47
  });
48
48
  });
49
+ describe("and translation key does contain pluralization with null content", function() {
50
+ beforeEach(function() {
51
+ translation_key = "sent";
52
+ });
53
+
54
+ it("return empty string", function() {
55
+ expect(I18n.t(translation_key, {count: 0})).toEqual('[missing "en.sent.zero" translation]');
56
+ expect(I18n.t(translation_key, {count: 1})).toEqual('[missing "en.sent.one" translation]');
57
+ expect(I18n.t(translation_key, {count: 5})).toEqual('[missing "en.sent.other" translation]');
58
+ });
59
+ });
49
60
  });
50
61
 
51
62
  describe("when count is NOT passed in", function() {
@@ -57,6 +57,16 @@ describe("Pluralization", function(){
57
57
  expect(I18n.p(0, "inbox")).toEqual("");
58
58
  });
59
59
 
60
+ it("returns missing message on null values", function(){
61
+ I18n.translations["en"]["sent"]["zero"] = null;
62
+ I18n.translations["en"]["sent"]["one"] = null;
63
+ I18n.translations["en"]["sent"]["other"] = null;
64
+
65
+ expect(I18n.p(0, "sent")).toEqual('[missing "en.sent.zero" translation]');
66
+ expect(I18n.p(1, "sent")).toEqual('[missing "en.sent.one" translation]');
67
+ expect(I18n.p(5, "sent")).toEqual('[missing "en.sent.other" translation]');
68
+ });
69
+
60
70
  it("pluralizes using custom rules", function() {
61
71
  I18n.locale = "custom";
62
72
 
@@ -103,4 +113,99 @@ describe("Pluralization", function(){
103
113
  expect(I18n.p(1, "inbox", options)).toEqual("You have 1 message");
104
114
  expect(I18n.p(5, "inbox", options)).toEqual("You have 5 messages");
105
115
  });
116
+
117
+ it("fallback to default locale when I18n.fallbacks is enabled", function() {
118
+ I18n.locale = "pt-BR";
119
+ I18n.fallbacks = true;
120
+ I18n.translations["pt-BR"].inbox= {
121
+ one: null
122
+ , other: null
123
+ , zero: null
124
+ };
125
+ expect(I18n.p(0, "inbox", { count: 0 })).toEqual("You have no messages");
126
+ expect(I18n.p(1, "inbox", { count: 1 })).toEqual("You have 1 message");
127
+ expect(I18n.p(5, "inbox", { count: 5 })).toEqual("You have 5 messages");
128
+ });
129
+
130
+ it("fallback to default locale when I18n.fallbacks is enabled", function() {
131
+ I18n.locale = "pt-BR";
132
+ I18n.fallbacks = true;
133
+ I18n.translations["pt-BR"].inbox= {
134
+ one: "Você tem uma mensagem"
135
+ , other: null
136
+ , zero: "Você não tem nenhuma mensagem"
137
+ };
138
+ expect(I18n.p(0, "inbox", { count: 0 })).toEqual("Você não tem nenhuma mensagem");
139
+ expect(I18n.p(1, "inbox", { count: 1 })).toEqual("Você tem uma mensagem");
140
+ expect(I18n.p(5, "inbox", { count: 5 })).toEqual('You have 5 messages');
141
+ });
142
+
143
+ it("fallback to 'other' scope", function() {
144
+ I18n.locale = "pt-BR";
145
+ I18n.fallbacks = true;
146
+ I18n.translations["pt-BR"].inbox= {
147
+ one: "Você tem uma mensagem"
148
+ , other: "Você tem {{count}} mensagens"
149
+ , zero: null
150
+ }
151
+ expect(I18n.p(0, "inbox", { count: 0 })).toEqual("Você tem 0 mensagens");
152
+ expect(I18n.p(1, "inbox", { count: 1 })).toEqual("Você tem uma mensagem");
153
+ expect(I18n.p(5, "inbox", { count: 5 })).toEqual("Você tem 5 mensagens");
154
+ });
155
+
156
+ it("fallback to defaulValue when defaultValue is string", function() {
157
+ I18n.locale = "pt-BR";
158
+ I18n.fallbacks = true;
159
+ I18n.translations["en"]["inbox"]["zero"] = null;
160
+ I18n.translations["en"]["inbox"]["one"] = null;
161
+ I18n.translations["en"]["inbox"]["other"] = null;
162
+ I18n.translations["pt-BR"].inbox= {
163
+ one: "Você tem uma mensagem"
164
+ , other: null
165
+ , zero: null
166
+ }
167
+ options = {
168
+ defaultValue: "default message"
169
+ };
170
+ expect(I18n.p(0, "inbox", options)).toEqual("default message");
171
+ expect(I18n.p(1, "inbox", options)).toEqual("Você tem uma mensagem");
172
+ expect(I18n.p(5, "inbox", options)).toEqual("default message");
173
+ });
174
+
175
+ it("fallback to defaulValue when defaultValue is an object", function() {
176
+ I18n.locale = "pt-BR";
177
+ I18n.fallbacks = true;
178
+ I18n.translations["en"]["inbox"]["zero"] = null;
179
+ I18n.translations["en"]["inbox"]["one"] = null;
180
+ I18n.translations["en"]["inbox"]["other"] = null;
181
+ I18n.translations["pt-BR"].inbox= {
182
+ one: "Você tem uma mensagem"
183
+ , other: null
184
+ , zero: null
185
+ }
186
+ options = {
187
+ defaultValue: {
188
+ zero: "default message for no message"
189
+ , one: "default message for 1 message"
190
+ , other: "default message for {{count}} messages"
191
+ }
192
+ };
193
+ expect(I18n.p(0, "inbox", options)).toEqual("default message for no message");
194
+ expect(I18n.p(1, "inbox", options)).toEqual("Você tem uma mensagem");
195
+ expect(I18n.p(5, "inbox", options)).toEqual("default message for 5 messages");
196
+ });
197
+
198
+ it("fallback to default locale when I18n.fallbacks is enabled and no translations in sub scope", function() {
199
+ I18n.locale = "pt-BR";
200
+ I18n.fallbacks = true;
201
+ I18n.translations["en"]["mailbox"] = {
202
+ inbox: I18n.translations["en"].inbox
203
+ }
204
+
205
+ expect(I18n.translations["pt-BR"]["mailbox"]).toEqual(undefined);
206
+ expect(I18n.p(0, "mailbox.inbox", { count: 0 })).toEqual("You have no messages");
207
+ expect(I18n.p(1, "mailbox.inbox", { count: 1 })).toEqual("You have 1 message");
208
+ expect(I18n.p(5, "mailbox.inbox", { count: 5 })).toEqual("You have 5 messages");
209
+ });
210
+
106
211
  });
@@ -152,6 +152,11 @@ describe("Translate", function(){
152
152
  expect(actual).toEqual("Warning!");
153
153
  });
154
154
 
155
+ it("uses default value for plural translation", function(){
156
+ actual = I18n.t("message", {defaultValue: { one: '%{count} message', other: '%{count} messages'}, count: 1});
157
+ expect(actual).toEqual("1 message");
158
+ });
159
+
155
160
  it("uses default value for unknown locale", function(){
156
161
  I18n.locale = "fr";
157
162
  actual = I18n.t("warning", {defaultValue: "Warning!"});
@@ -30,6 +30,12 @@ var DEBUG = false;
30
30
  , zero: "You have no messages"
31
31
  }
32
32
 
33
+ , sent: {
34
+ one: null
35
+ , other: null
36
+ , zero: null
37
+ }
38
+
33
39
  , unread: {
34
40
  one: "You have 1 new message ({{unread}} unread)"
35
41
  , other: "You have {{count}} new messages ({{unread}} unread)"
@@ -42,7 +42,7 @@ describe I18n::JS::FallbackLocales do
42
42
 
43
43
  context "when given a invalid locale as fallbacks" do
44
44
  let(:fallbacks) { :invalid_locale }
45
- it { expect(fetching_locales).to raise_error(ArgumentError) }
45
+ it { should eq([:invalid_locale]) }
46
46
  end
47
47
 
48
48
  context "when given a invalid type as fallbacks" do
@@ -50,16 +50,6 @@ describe I18n::JS::FallbackLocales do
50
50
  it { expect(fetching_locales).to raise_error(ArgumentError) }
51
51
  end
52
52
 
53
- context "when given an invalid Array as fallbacks" do
54
- let(:fallbacks) { [:de, :en, :invalid_locale] }
55
- it { expect(fetching_locales).to raise_error(ArgumentError) }
56
- end
57
-
58
- context "when given a invalid Hash as fallbacks" do
59
- let(:fallbacks) do { :fr => [:de, :en, :invalid_locale] } end
60
- it { expect(fetching_locales).to raise_error(ArgumentError) }
61
- end
62
-
63
53
  # I18n::Backend::Fallbacks
64
54
  context "when I18n::Backend::Fallbacks is used" do
65
55
  let(:backend_with_fallbacks) { backend_class_with_fallbacks.new }
@@ -405,7 +405,7 @@ EOS
405
405
  it "executes erb in config file" do
406
406
  set_config "erb.yml"
407
407
 
408
- config_entry = I18n::JS.config["translations"].first
408
+ config_entry = I18n::JS.config[:translations].first
409
409
  config_entry["only"].should eq("*.date.formats")
410
410
  end
411
411
  end
data/spec/spec_helper.rb CHANGED
@@ -1,14 +1,16 @@
1
1
  require "i18n"
2
2
  require "json"
3
3
 
4
- require "active_support/all"
5
4
  require "i18n/js"
6
5
 
7
6
  module Helpers
8
7
  # Set the configuration as the current one
9
8
  def set_config(path)
10
9
  config_file_path = File.dirname(__FILE__) + "/fixtures/#{path}"
11
- I18n::JS.stub(:config? => true, :config_file_path => config_file_path)
10
+ allow(I18n::JS).to receive_messages(
11
+ :config? => true,
12
+ :config_file_path => config_file_path,
13
+ )
12
14
  end
13
15
 
14
16
  # Shortcut to I18n::JS.translations
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: i18n-js
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.0.0.rc14
4
+ version: 3.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Nando Vieira
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2016-08-29 00:00:00.000000000 Z
11
+ date: 2017-04-01 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: i18n
@@ -44,20 +44,6 @@ dependencies:
44
44
  - - "~>"
45
45
  - !ruby/object:Gem::Version
46
46
  version: '2.0'
47
- - !ruby/object:Gem::Dependency
48
- name: activesupport
49
- requirement: !ruby/object:Gem::Requirement
50
- requirements:
51
- - - ">="
52
- - !ruby/object:Gem::Version
53
- version: 3.2.22
54
- type: :development
55
- prerelease: false
56
- version_requirements: !ruby/object:Gem::Requirement
57
- requirements:
58
- - - ">="
59
- - !ruby/object:Gem::Version
60
- version: 3.2.22
61
47
  - !ruby/object:Gem::Dependency
62
48
  name: rspec
63
49
  requirement: !ruby/object:Gem::Requirement
@@ -78,14 +64,14 @@ dependencies:
78
64
  requirements:
79
65
  - - "~>"
80
66
  - !ruby/object:Gem::Version
81
- version: '10.0'
67
+ version: '12.0'
82
68
  type: :development
83
69
  prerelease: false
84
70
  version_requirements: !ruby/object:Gem::Requirement
85
71
  requirements:
86
72
  - - "~>"
87
73
  - !ruby/object:Gem::Version
88
- version: '10.0'
74
+ version: '12.0'
89
75
  - !ruby/object:Gem::Dependency
90
76
  name: gem-release
91
77
  requirement: !ruby/object:Gem::Requirement
@@ -120,6 +106,7 @@ files:
120
106
  - app/assets/javascripts/i18n/translations.js
121
107
  - gemfiles/i18n_0_6.gemfile
122
108
  - gemfiles/i18n_0_7.gemfile
109
+ - gemfiles/i18n_0_8.gemfile
123
110
  - i18n-js.gemspec
124
111
  - lib/i18n-js.rb
125
112
  - lib/i18n/js.rb
@@ -127,6 +114,7 @@ files:
127
114
  - lib/i18n/js/engine.rb
128
115
  - lib/i18n/js/fallback_locales.rb
129
116
  - lib/i18n/js/middleware.rb
117
+ - lib/i18n/js/private/hash_with_symbol_keys.rb
130
118
  - lib/i18n/js/segment.rb
131
119
  - lib/i18n/js/utils.rb
132
120
  - lib/i18n/js/version.rb
@@ -200,15 +188,15 @@ required_ruby_version: !ruby/object:Gem::Requirement
200
188
  requirements:
201
189
  - - ">="
202
190
  - !ruby/object:Gem::Version
203
- version: 1.9.3
191
+ version: 2.1.0
204
192
  required_rubygems_version: !ruby/object:Gem::Requirement
205
193
  requirements:
206
- - - ">"
194
+ - - ">="
207
195
  - !ruby/object:Gem::Version
208
- version: 1.3.1
196
+ version: '0'
209
197
  requirements: []
210
198
  rubyforge_project:
211
- rubygems_version: 2.6.6
199
+ rubygems_version: 2.6.11
212
200
  signing_key:
213
201
  specification_version: 4
214
202
  summary: It's a small library to provide the Rails I18n translations on the Javascript.
@@ -267,4 +255,3 @@ test_files:
267
255
  - spec/ruby/i18n/js/utils_spec.rb
268
256
  - spec/ruby/i18n/js_spec.rb
269
257
  - spec/spec_helper.rb
270
- has_rdoc: