i18n-js 3.0.0.rc9 → 3.0.0.rc10

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: 6e722b7f34475d50126d5ba7556c9a517f65266f
4
- data.tar.gz: 668e6660683a5eb93b2812f656af2439237299e7
3
+ metadata.gz: 0b54325d787e4471fabea3b33981f0b4ada0e660
4
+ data.tar.gz: 94c1e7e0ef79bfb984f9725453c40aef5290d3e6
5
5
  SHA512:
6
- metadata.gz: c0cdc594d00611b72e9ca9327389d9f90b5b4d2990b8ecc3f144b44dfaf03aa793b01acc6a28aa57e46c492fe923eefa5b879696e654a7c689486d46258a4a84
7
- data.tar.gz: 9ae2cea1e167dc49edf962a1ccb0d0ceb153a7d53e5207bd8c830deed4b1a58a9d19b41b9cdae209fefb385c68798412925fb85874ea02e8670b7081ae609095
6
+ metadata.gz: 63dedf3a5d8d7f144773e922ba113d5586c1eb608f5745608a363dd3b5604acd38863a4978bd1bce091bfc6b033ad85bf59915985b790d96beafa7681d739833
7
+ data.tar.gz: 30ee692d8f29ad98fc71014e119d41ae82490cddbd1a21a808cff54c97c25bf0c3abda5102555684c131a191e302eb93cd7fe98559dbc8aee2c8df9912d5fd79
data/.travis.yml CHANGED
@@ -1,13 +1,15 @@
1
+ # Send builds to container-based infrastructure
2
+ # http://docs.travis-ci.com/user/workers/container-based-infrastructure/
3
+ sudo: false
1
4
  language: ruby
5
+ cache:
6
+ - bundler
2
7
  rvm:
3
8
  - 2.0
4
9
  - 2.1
5
10
  - 2.2
6
11
  - ruby-head
7
- script: "bundle exec rake spec"
8
- before_install: # Need to install npm to test js
9
- - sudo apt-get update
10
- - sudo apt-get install npm
12
+ before_install: # Need to install something extra to test JS
11
13
  - npm install jasmine-node@1.14.2
12
14
  gemfile:
13
15
  - gemfiles/i18n_0_6.gemfile
data/CHANGELOG.md CHANGED
@@ -1,6 +1,32 @@
1
1
 
2
2
  ## Unreleased
3
3
 
4
+ ### breaking changes
5
+
6
+ ### enhancements
7
+
8
+ ### bug fixes
9
+
10
+
11
+ ## 3.0.0.rc10
12
+
13
+ ### breaking changes
14
+
15
+ - [Ruby] In `config/i18n-js.yml`, if you are using `%{locale}` in your filename and are referencing specific translations keys, please add `*.` to the beginning of those keys. ([#320](https://github.com/fnando/i18n-js/pull/320))
16
+ - [Ruby] The `:except` option to exclude certain phrases now (only) accepts the same patterns the `:only` option accepts
17
+
18
+ ### enhancements
19
+
20
+ - [Ruby] Make handling of per-locale and not-per-locale exporting to be more consistent ([#320](https://github.com/fnando/i18n-js/pull/320))
21
+ - [Ruby] Add option `sort_translation_keys` to sort translation keys alphabetically ([#318](https://github.com/fnando/i18n-js/pull/318))
22
+
23
+ ### bug fixes
24
+
25
+ - [Ruby] Fix fallback logic to work with not-per-locale files ([#320](https://github.com/fnando/i18n-js/pull/320))
26
+
27
+
28
+ ## 3.0.0.rc9
29
+
4
30
  ### enhancements
5
31
 
6
32
  - [JS] Force currency number sign to be at first place using `sign_first` option, default to `true`
@@ -10,13 +36,15 @@
10
36
  - [Ruby] Add `:except` option to exclude certain phrases or groups of phrases from the
11
37
  outputted translations ([#312](https://github.com/fnando/i18n-js/pull/312))
12
38
  - [JS] You can now set `I18n.missingBehavior='guess'` to have the scope string output as text instead of of the
13
- "[missing `scope`]" message when no translation is available.
14
- - [JS] Combined that with `I18n.missingTranslationPrefix='SOMETHING'` and you can
39
+ "[missing `scope`]" message when no translation is available.
40
+ Combined that with `I18n.missingTranslationPrefix='SOMETHING'` and you can
15
41
  still identify those missing strings.
42
+ ([#304](https://github.com/fnando/i18n-js/pull/304))
16
43
 
17
44
  ### bug fixes
18
45
 
19
46
  - [JS] Fix missing translation message when scope is passed in options
47
+ - [Ruby] Fix save cache directory verification when path is a symbolic link ([#329](https://github.com/fnando/i18n-js/pull/329))
20
48
 
21
49
 
22
50
  ## 3.0.0.rc8
data/README.md CHANGED
@@ -38,6 +38,8 @@ then you must add the following line to your `app/assets/javascripts/application
38
38
  // This is optional (in case you have `I18n is not defined` error)
39
39
  // If you want to put this line, you must put it BEFORE `i18n/translations`
40
40
  //= require i18n
41
+ // Some people even need to add the extension to make it work, see https://github.com/fnando/i18n-js/issues/283
42
+ //= require i18n.js
41
43
  //
42
44
  // This is a must
43
45
  //= require i18n/translations
@@ -97,7 +99,7 @@ translations:
97
99
  - file: "public/javascripts/i18n/%{locale}.js"
98
100
  only: '*'
99
101
  - file: "public/javascripts/frontend/i18n/%{locale}.js"
100
- only: ['frontend', 'users']
102
+ only: ['*.frontend', '*.users.*']
101
103
  ```
102
104
 
103
105
  You can also include ERB in your config file.
@@ -116,7 +118,7 @@ keys listed in `except` configuration param:
116
118
 
117
119
  ```yaml
118
120
  translations:
119
- - except: ['active_admin', 'ransack']
121
+ - except: ['*.active_admin', '*.ransack', '*.activerecord.errors']
120
122
  ```
121
123
 
122
124
 
@@ -134,16 +136,25 @@ translations:
134
136
  - Any `String`: considered as a relative path for a folder to `Rails.root` and export `i18n.js` to that folder for `rake i18n:js:export`
135
137
  - Any non-`String` (`nil`, `false`, `:none`, etc): Disable `i18n.js` exporting
136
138
 
137
- - You may also set `export_i18n_js` in your config file, e.g.:
139
+ - `I18n::JS.sort_translation_keys`
140
+ Expected Type: `Boolean`
141
+ Default: `true`
142
+ Behaviour:
143
+ - Sets whether or not to deep sort all translation keys in order to generate identical output for the same translations
144
+ - Set to true to ensure identical asset fingerprints for the asset pipeline
145
+
146
+ - You may also set `export_i18n_js` and `sort_translation_keys` in your config file, e.g.:
138
147
 
139
148
  ```yaml
140
149
  export_i18n_js_: false
141
150
  # OR
142
151
  export_i18n_js: "my/path"
143
152
 
153
+ sort_translation_keys: false
154
+
144
155
  translations:
145
156
  - ...
146
- ``
157
+ ```
147
158
 
148
159
  To find more examples on how to use the configuration file please refer to the tests.
149
160
 
@@ -358,15 +369,22 @@ becomes "what is your favorite Christmas present"
358
369
 
359
370
  In order to still detect untranslated strings, you can
360
371
  i18n.missingTranslationPrefix to something like:
361
-
362
- I18n.missingTranslationPrefix = 'EE: '
372
+ ```javascript
373
+ I18n.missingTranslationPrefix = 'EE: ';
374
+ ```
363
375
 
364
376
  And result will be:
365
-
366
- "EE: what is your favorite Christmas present"
377
+ ```javascript
378
+ "EE: what is your favorite Christmas present"
379
+ ```
367
380
 
368
381
  This will help you doing automated tests against your localisation assets.
369
382
 
383
+ Some people prefer returning `null` for missing translation:
384
+ ```javascript
385
+ I18n.missingTranslation = function () { return undefined; };
386
+ ```
387
+
370
388
  Pluralization is possible as well and by default provides English rules:
371
389
 
372
390
  I18n.t("inbox.counting", {count: 10}); // You have 10 messages
@@ -18,7 +18,8 @@
18
18
  module.exports = factory(this);
19
19
  } else if (typeof define === 'function' && define.amd) {
20
20
  // AMD
21
- define('i18n', (function(global){ return function(){ return factory(global); }})(this));
21
+ var global=this;
22
+ define('i18n', function(){ return factory(global);});
22
23
  } else {
23
24
  // Browser globals
24
25
  this.I18n = factory(this);
data/i18n-js.gemspec CHANGED
@@ -19,7 +19,7 @@ Gem::Specification.new do |s|
19
19
  s.require_paths = ["lib"]
20
20
 
21
21
  s.add_dependency "i18n", "~> 0.6"
22
- s.add_development_dependency "appraisal", "~> 1.0"
22
+ s.add_development_dependency "appraisal", "~> 2.0"
23
23
  s.add_development_dependency "activesupport", ">= 3"
24
24
  s.add_development_dependency "rspec", "~> 3.0"
25
25
  s.add_development_dependency "rake"
data/lib/i18n/js.rb CHANGED
@@ -34,17 +34,6 @@ module I18n
34
34
  translation_segments.each(&:save!)
35
35
  end
36
36
 
37
- def self.segments_per_locale(pattern, scope, exceptions, options)
38
- I18n.available_locales.each_with_object([]) do |locale, segments|
39
- scope = [scope] unless scope.respond_to?(:each)
40
- result = scoped_translations(scope.collect{|s| "#{locale}.#{s}"}, exceptions)
41
- merge_with_fallbacks!(result, locale, scope, exceptions) if use_fallbacks?
42
-
43
- next if result.empty?
44
- segments << Segment.new(::I18n.interpolate(pattern, {:locale => locale}), result, options)
45
- end
46
- end
47
-
48
37
  def self.segment_for_scope(scope, exceptions)
49
38
  if scope == "*"
50
39
  exclude(translations, exceptions)
@@ -61,23 +50,34 @@ module I18n
61
50
 
62
51
  segment_options = options.slice(:namespace, :pretty_print)
63
52
 
64
- if file =~ ::I18n::INTERPOLATION_PATTERN
65
- segments += segments_per_locale(file, only, exceptions, segment_options)
66
- else
67
- result = segment_for_scope(only, exceptions)
68
- segments << Segment.new(file, result, segment_options) unless result.empty?
69
- end
53
+ result = segment_for_scope(only, exceptions)
54
+
55
+ merge_with_fallbacks!(result) if fallbacks
56
+
57
+ segments << Segment.new(file, result, segment_options) unless result.empty?
70
58
 
71
59
  segments
72
60
  end
73
61
  end
74
62
 
63
+ # deep_merge! given result with result for fallback locale
64
+ def self.merge_with_fallbacks!(result)
65
+ I18n.available_locales.each do |locale|
66
+ fallback_locales = FallbackLocales.new(fallbacks, locale)
67
+ fallback_locales.each do |fallback_locale|
68
+ result[locale] = Utils.deep_merge(result[fallback_locale], result[locale] || {})
69
+ end
70
+ end
71
+ end
72
+
75
73
  def self.filtered_translations
76
- {}.tap do |result|
74
+ translations = {}.tap do |result|
77
75
  translation_segments.each do |segment|
78
76
  Utils.deep_merge!(result, segment.translations)
79
77
  end
80
78
  end
79
+ return Utils.deep_key_sort(translations) if I18n::JS.sort_translation_keys?
80
+ translations
81
81
  end
82
82
 
83
83
  def self.translation_segments
@@ -109,9 +109,9 @@ module I18n
109
109
 
110
110
  [scopes].flatten.each do |scope|
111
111
  translations_without_exceptions = exclude(translations, exceptions)
112
- filtered_translations = filter(translations_without_exceptions, scope)
112
+ filtered_translations = filter(translations_without_exceptions, scope) || {}
113
113
 
114
- Utils.deep_merge! result, filtered_translations
114
+ Utils.deep_merge!(result, filtered_translations)
115
115
  end
116
116
 
117
117
  result
@@ -122,8 +122,9 @@ module I18n
122
122
  return translations if exceptions.empty?
123
123
 
124
124
  exceptions.inject(translations) do |memo, exception|
125
- Utils.deep_reject(memo) do |key, value|
126
- key.to_s == exception.to_s
125
+ exception_scopes = exception.to_s.split(".")
126
+ Utils.deep_reject(memo) do |key, value, scopes|
127
+ Utils.scopes_match?(scopes, exception_scopes)
127
128
  end
128
129
  end
129
130
  end
@@ -166,15 +167,14 @@ module I18n
166
167
  end
167
168
  end
168
169
 
169
- # deep_merge! given result with result for fallback locale
170
- def self.merge_with_fallbacks!(result, locale, scope, exceptions)
171
- result[locale] ||= {}
172
- fallback_locales = FallbackLocales.new(fallbacks, locale)
170
+ def self.sort_translation_keys?
171
+ @sort_translation_keys ||= (config[:sort_translation_keys]) if config.has_key?(:sort_translation_keys)
172
+ @sort_translation_keys = true if @sort_translation_keys.nil?
173
+ @sort_translation_keys
174
+ end
173
175
 
174
- fallback_locales.each do |fallback_locale|
175
- fallback_result = scoped_translations(scope.collect{|s| "#{fallback_locale}.#{s}"}, exceptions) # NOTE: Duplicated code here
176
- result[locale] = Utils.deep_merge(fallback_result[fallback_locale], result[locale])
177
- end
176
+ def self.sort_translation_keys=(value)
177
+ @sort_translation_keys = !!value
178
178
  end
179
179
 
180
180
  ### Export i18n.js
@@ -31,7 +31,8 @@ module I18n
31
31
  end
32
32
 
33
33
  def save_cache(new_cache)
34
- FileUtils.mkdir_p(cache_dir)
34
+ # path could be a symbolic link
35
+ FileUtils.mkdir_p(cache_dir) unless File.exists?(cache_dir)
35
36
  File.open(cache_path, "w+") do |file|
36
37
  file << new_cache.to_yaml
37
38
  end
@@ -5,6 +5,8 @@ module I18n
5
5
  class Segment
6
6
  attr_accessor :file, :translations, :namespace, :pretty_print
7
7
 
8
+ LOCALE_INTERPOLATOR = /%\{locale\}/
9
+
8
10
  def initialize(file, translations, options = {})
9
11
  @file = file
10
12
  @translations = translations
@@ -14,18 +16,28 @@ module I18n
14
16
 
15
17
  # Saves JSON file containing translations
16
18
  def save!
17
- FileUtils.mkdir_p File.dirname(self.file)
18
-
19
- File.open(self.file, "w+") do |f|
20
- f << %(#{self.namespace}.translations || (#{self.namespace}.translations = {});\n)
21
- self.translations.each do |locale, translations|
22
- f << %(#{self.namespace}.translations["#{locale}"] = #{print_json(translations)};\n);
19
+ if self.file =~ LOCALE_INTERPOLATOR
20
+ I18n.available_locales.each do |locale|
21
+ write_file(file_for_locale(locale), self.translations.slice(locale))
23
22
  end
23
+ else
24
+ write_file
24
25
  end
25
26
  end
26
27
 
27
28
  protected
28
29
 
30
+ def write_file(_file = self.file, _translations = self.translations)
31
+ FileUtils.mkdir_p File.dirname(_file)
32
+ File.open(_file, "w+") do |f|
33
+ f << %(#{self.namespace}.translations || (#{self.namespace}.translations = {});\n)
34
+ _translations.each do |locale, translations_for_locale|
35
+ output_translations = I18n::JS.sort_translation_keys? ? Utils.deep_key_sort(translations_for_locale) : translations_for_locale
36
+ f << %(#{self.namespace}.translations["#{locale}"] = #{print_json(output_translations)};\n);
37
+ end
38
+ end
39
+ end
40
+
29
41
  # Outputs pretty or ugly JSON depending on :pretty_print option
30
42
  def print_json(translations)
31
43
  if pretty_print
@@ -34,6 +46,11 @@ module I18n
34
46
  translations.to_json
35
47
  end
36
48
  end
49
+
50
+ # interpolates filename
51
+ def file_for_locale(locale)
52
+ self.file.gsub(LOCALE_INTERPOLATOR, locale.to_s)
53
+ end
37
54
  end
38
55
  end
39
56
  end
data/lib/i18n/js/utils.rb CHANGED
@@ -22,13 +22,30 @@ module I18n
22
22
  target_hash.merge!(hash, &MERGER)
23
23
  end
24
24
 
25
- def self.deep_reject(hash, &block)
25
+ def self.deep_reject(hash, scopes = [], &block)
26
26
  hash.each_with_object({}) do |(k, v), memo|
27
- unless block.call(k, v)
28
- memo[k] = v.kind_of?(Hash) ? deep_reject(v, &block) : v
27
+ unless block.call(k, v, scopes + [k.to_s])
28
+ memo[k] = v.kind_of?(Hash) ? deep_reject(v, scopes + [k.to_s], &block) : v
29
29
  end
30
30
  end
31
31
  end
32
+
33
+ def self.scopes_match?(scopes1, scopes2)
34
+ if scopes1.length == scopes2.length
35
+ [scopes1, scopes2].transpose.all? do |scope1, scope2|
36
+ scope1.to_s == '*' || scope2.to_s == '*' || scope1.to_s == scope2.to_s
37
+ end
38
+ end
39
+ end
40
+
41
+ def self.deep_key_sort(hash)
42
+ # Avoid things like `true` or `1` from YAML which causes error
43
+ hash.keys.sort {|a, b| a.to_s <=> b.to_s}.
44
+ each_with_object({}) do |key, seed|
45
+ value = hash[key]
46
+ seed[key] = value.is_a?(Hash) ? deep_key_sort(value) : value
47
+ end
48
+ end
32
49
  end
33
50
  end
34
51
  end
@@ -4,7 +4,7 @@ module I18n
4
4
  MAJOR = 3
5
5
  MINOR = 0
6
6
  PATCH = 0
7
- STRING = "#{MAJOR}.#{MINOR}.#{PATCH}.rc9"
7
+ STRING = "#{MAJOR}.#{MINOR}.#{PATCH}.rc10"
8
8
  end
9
9
  end
10
10
  end
@@ -9,8 +9,7 @@
9
9
  # so.
10
10
  #
11
11
  # For more informations about the export options with this file, please
12
- # refer to the `Export Configuration` section in the README at :
13
- # https://github.com/fnando/i18n-js#export-configuration
12
+ # refer to the README
14
13
  #
15
14
  #
16
15
  # If you're going to use the Rails 3.1 asset pipeline, change
@@ -1,4 +1,5 @@
1
- # Find more details about this configuration file at http://github.com/fnando/i18n-js
1
+ fallbacks: false
2
+
2
3
  translations:
3
4
  - file: "tmp/i18n-js/all.js"
4
5
  only: "*"
@@ -1,4 +1,5 @@
1
- # Find more details about this configuration file at http://github.com/fnando/i18n-js
1
+ fallbacks: false
2
+
2
3
  translations:
3
4
  - file: "tmp/i18n-js/translations.js"
4
5
  only: "*"
@@ -1,4 +1,5 @@
1
- # Find more details about this configuration file at http://github.com/fnando/i18n-js
1
+ fallbacks: false
2
+
2
3
  translations:
3
4
  - file: "tmp/i18n-js/translations.js"
4
5
  only: '<%= "*." + "date." + "formats" %>'
@@ -1,3 +1,5 @@
1
+ fallbacks: false
2
+
1
3
  translations:
2
4
  - file: "tmp/i18n-js/trimmed.js"
3
5
  except:
@@ -1,3 +1,4 @@
1
+ fallbacks: false
1
2
 
2
3
  export_i18n_js: 'tmp/i18n-js/foo'
3
4
 
@@ -1,4 +1,4 @@
1
-
1
+ fallbacks: false
2
2
  export_i18n_js: false
3
3
 
4
4
  translations:
@@ -1,3 +1,7 @@
1
+ fallbacks: false
2
+
1
3
  translations:
2
4
  - file: "tmp/i18n-js/%{locale}.js"
3
- only: '*'
5
+ only:
6
+ - '*.date.*'
7
+ - '*.admin.*'
@@ -1,3 +1,5 @@
1
+ fallbacks: false
2
+
1
3
  translations:
2
4
  - file: "tmp/i18n-js/%{locale}.js"
3
5
  only: '*'
@@ -0,0 +1,6 @@
1
+
2
+ sort_translation_keys: false
3
+
4
+ translations:
5
+ - file: "tmp/i18n-js/%{locale}.js"
6
+ only: '*'
@@ -0,0 +1,6 @@
1
+
2
+ sort_translation_keys: true
3
+
4
+ translations:
5
+ - file: "tmp/i18n-js/%{locale}.js"
6
+ only: '*'
@@ -1,3 +1,5 @@
1
+ fallbacks: false
2
+
1
3
  translations:
2
4
  - file: "tmp/i18n-js/bitsnpieces.js"
3
5
  only:
@@ -1,5 +1,7 @@
1
+ fallbacks: false
2
+
1
3
  translations:
2
4
  - file: "tmp/i18n-js/bits.%{locale}.js"
3
5
  only:
4
- - "date.formats"
5
- - "number.currency"
6
+ - "*.date.formats.*"
7
+ - "*.number.currency.*"
@@ -1,4 +1,5 @@
1
- # Find more details about this configuration file at http://github.com/fnando/i18n-js
1
+ fallbacks: false
2
+
2
3
  translations:
3
4
  - file: "tmp/i18n-js/all.js"
4
5
  only: "*"
@@ -1,3 +1,4 @@
1
- # Find more details about this configuration file at http://github.com/fnando/i18n-js
1
+ fallbacks: false
2
+
2
3
  translations:
3
4
  - file: "tmp/i18n-js/no_scope.js"
@@ -1,4 +1,5 @@
1
- # Find more details about this configuration file at http://github.com/fnando/i18n-js
1
+ fallbacks: false
2
+
2
3
  translations:
3
4
  - file: "tmp/i18n-js/simple_scope.js"
4
5
  only: "*.date.formats"
data/spec/i18n_js_spec.rb CHANGED
@@ -1,6 +1,7 @@
1
1
  require "spec_helper"
2
2
 
3
3
  describe I18n::JS do
4
+
4
5
  describe '.config_file_path' do
5
6
  let(:default_path) { I18n::JS::DEFAULT_CONFIG_PATH }
6
7
  let(:new_path) { File.join("tmp", default_path) }
@@ -62,6 +63,19 @@ describe I18n::JS do
62
63
 
63
64
  file_should_exist "en.js"
64
65
  file_should_exist "fr.js"
66
+
67
+ en_output = File.read(File.join(I18n::JS.export_i18n_js_dir_path, "en.js"))
68
+ expect(en_output).to eq(<<EOS
69
+ I18n.translations || (I18n.translations = {});
70
+ I18n.translations["en"] = {"admin":{"edit":{"title":"Edit"},"show":{"note":"more details","title":"Show"}},"date":{"abbr_day_names":["Sun","Mon","Tue","Wed","Thu","Fri","Sat"],"abbr_month_names":[null,"Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"],"day_names":["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"],"formats":{"default":"%Y-%m-%d","long":"%B %d, %Y","short":"%b %d"},"month_names":[null,"January","February","March","April","May","June","July","August","September","October","November","December"]}};
71
+ EOS
72
+ )
73
+ fr_output = File.read(File.join(I18n::JS.export_i18n_js_dir_path, "fr.js"))
74
+ expect(fr_output).to eq(<<EOS
75
+ I18n.translations || (I18n.translations = {});
76
+ I18n.translations["fr"] = {"admin":{"edit":{"title":"Editer"},"show":{"note":"plus de détails","title":"Visualiser"}},"date":{"abbr_day_names":["dim","lun","mar","mer","jeu","ven","sam"],"abbr_month_names":[null,"jan.","fév.","mar.","avr.","mai","juin","juil.","août","sept.","oct.","nov.","déc."],"day_names":["dimanche","lundi","mardi","mercredi","jeudi","vendredi","samedi"],"formats":{"default":"%d/%m/%Y","long":"%e %B %Y","long_ordinal":"%e %B %Y","only_day":"%e","short":"%e %b"},"month_names":[null,"janvier","février","mars","avril","mai","juin","juillet","août","septembre","octobre","novembre","décembre"]}};
77
+ EOS
78
+ )
65
79
  end
66
80
 
67
81
  it "exports with multiple conditions" do
@@ -77,13 +91,22 @@ describe I18n::JS do
77
91
  set_config "multiple_conditions_per_locale.yml"
78
92
 
79
93
  result = I18n::JS.translation_segments
80
- result.map(&:file).should eql(["tmp/i18n-js/bits.en.js", "tmp/i18n-js/bits.fr.js"])
94
+ result.map(&:file).should eql(["tmp/i18n-js/bits.%{locale}.js"])
81
95
 
82
- %w(en fr).each do |lang|
83
- segment = result.select{|s| s.file == "tmp/i18n-js/bits.#{lang}.js"}.first
84
- segment.translations.keys.should eql([lang.to_sym])
85
- segment.translations[lang.to_sym].keys.sort.should eql([:date, :number])
86
- end
96
+ result.map(&:save!)
97
+
98
+ en_output = File.read(File.join(I18n::JS.export_i18n_js_dir_path, "bits.en.js"))
99
+ expect(en_output).to eq(<<EOS
100
+ I18n.translations || (I18n.translations = {});
101
+ I18n.translations["en"] = {"date":{"formats":{"default":"%Y-%m-%d","long":"%B %d, %Y","short":"%b %d"}},"number":{"currency":{"format":{"delimiter":",","format":"%u%n","precision":2,"separator":".","unit":"$"}}}};
102
+ EOS
103
+ )
104
+ fr_output = File.read(File.join(I18n::JS.export_i18n_js_dir_path, "bits.fr.js"))
105
+ expect(fr_output).to eq(<<EOS
106
+ I18n.translations || (I18n.translations = {});
107
+ I18n.translations["fr"] = {"date":{"formats":{"default":"%d/%m/%Y","long":"%e %B %Y","long_ordinal":"%e %B %Y","only_day":"%e","short":"%e %b"}},"number":{"currency":{"format":{"format":"%n %u","precision":2,"unit":"€"}}}};
108
+ EOS
109
+ )
87
110
  end
88
111
 
89
112
  it "exports with :except condition" do
@@ -138,69 +161,126 @@ describe I18n::JS do
138
161
  result[:en][:admin][:edit][:title].should eql("Edit")
139
162
  result[:fr][:admin][:edit][:title].should eql("Editer")
140
163
  end
164
+
165
+ describe ".filtered_translations" do
166
+ subject do
167
+ I18n::JS.filtered_translations
168
+ end
169
+
170
+ let!(:old_sort_translation_keys) { I18n::JS.sort_translation_keys? }
171
+ before { I18n::JS.sort_translation_keys = sort_translation_keys_value }
172
+ after { I18n::JS.sort_translation_keys = old_sort_translation_keys }
173
+ before { expect(I18n::JS.sort_translation_keys?).to eq(sort_translation_keys_value) }
174
+
175
+ let(:sorted_hash) do
176
+ {sorted: :hash}
177
+ end
178
+ before do
179
+ allow(I18n::JS::Utils).
180
+ to receive(:deep_key_sort).
181
+ and_return(sorted_hash)
182
+ end
183
+
184
+ shared_examples_for ".filtered_translations" do
185
+ subject do
186
+ I18n::JS.filtered_translations
187
+ end
188
+
189
+ # This example is to prevent the regression from
190
+ # PR https://github.com/fnando/i18n-js/pull/318
191
+ it {should be_a(Hash)}
192
+ # Might need to test the keys... or not
193
+ end
194
+
195
+ context "when translation keys SHOULD be sorted" do
196
+ let(:sort_translation_keys_value) { true }
197
+
198
+ it_behaves_like ".filtered_translations"
199
+ it {should eq(sorted_hash)}
200
+ end
201
+ context "when translation keys should NOT be sorted" do
202
+ let(:sort_translation_keys_value) { false }
203
+
204
+ it_behaves_like ".filtered_translations"
205
+ it {should_not eq(sorted_hash)}
206
+ end
207
+ end
141
208
  end
142
209
 
143
210
  context "exceptions" do
144
- it "does not include a key listed in the exceptions list" do
145
- result = I18n::JS.scoped_translations("*", ['admin'])
211
+ it "does not include scopes listed in the exceptions list" do
212
+ result = I18n::JS.scoped_translations("*", ['de.*', '*.admin', '*.*.currency'])
213
+
214
+ result[:de].should be_empty
146
215
 
147
216
  result[:en][:admin].should be_nil
148
217
  result[:fr][:admin].should be_nil
218
+ result[:ja][:admin].should be_nil
219
+
220
+ result[:en][:number][:currency].should be_nil
221
+ result[:fr][:number][:currency].should be_nil
149
222
  end
150
223
 
151
- it "does not include multiple keys listed in the exceptions list" do
152
- result = I18n::JS.scoped_translations("*", ['title', 'note'])
224
+ it "does not include scopes listed in the exceptions list and respects the 'only' option" do
225
+ result = I18n::JS.scoped_translations("fr.*", ['*.admin', '*.*.currency'])
226
+
227
+ result[:en].should be_nil
228
+ result[:de].should be_nil
229
+ result[:ja].should be_nil
153
230
 
154
- result[:en][:admin][:show].should be_empty
155
- result[:en][:admin][:edit].should be_empty
231
+ result[:fr][:admin].should be_nil
232
+ result[:fr][:number][:currency].should be_nil
156
233
 
157
- result[:fr][:admin][:show].should be_empty
158
- result[:fr][:admin][:show].should be_empty
159
- result[:fr][:admin][:edit].should be_empty
234
+ result[:fr][:time][:am].should be_a(String)
160
235
  end
161
236
 
162
- it "does not include a key listed in the exceptions list and respecs the 'only' option" do
163
- result = I18n::JS.scoped_translations("fr.*", ['date', 'time', 'number', 'show'])
237
+ it "does exclude absolute scopes listed in the exceptions list" do
238
+ result = I18n::JS.scoped_translations("*", ['de', 'en.admin', 'fr.number.currency'])
164
239
 
165
- result[:en].should be_nil
166
240
  result[:de].should be_nil
167
- result[:ja].should be_nil
168
241
 
169
- result[:fr][:date].should be_nil
170
- result[:fr][:time].should be_nil
171
- result[:fr][:number].should be_nil
172
- result[:fr][:admin][:show].should be_nil
242
+ result[:en].should be_a(Hash)
243
+ result[:en][:admin].should be_nil
244
+
245
+ result[:fr][:number].should be_a(Hash)
246
+ result[:fr][:number][:currency].should be_nil
247
+ end
248
+
249
+ it "does not exclude non-absolute scopes listed in the exceptions list" do
250
+ result = I18n::JS.scoped_translations("*", ['admin', 'currency'])
173
251
 
174
- result[:fr][:admin][:edit][:title].should be_a(String)
252
+ result[:en][:admin].should be_a(Hash)
253
+ result[:fr][:admin].should be_a(Hash)
254
+ result[:ja][:admin].should be_a(Hash)
255
+
256
+ result[:en][:number][:currency].should be_a(Hash)
257
+ result[:fr][:number][:currency].should be_a(Hash)
175
258
  end
176
259
  end
177
260
 
178
261
  context "fallbacks" do
179
262
  subject do
180
- I18n::JS.translation_segments.inject({}) do |hash, segment|
181
- hash[segment.file] = segment.translations
182
- hash
183
- end
263
+ I18n::JS.translation_segments.first.translations
184
264
  end
185
265
 
186
266
  it "exports without fallback when disabled" do
187
267
  set_config "js_file_per_locale_without_fallbacks.yml"
188
- subject["tmp/i18n-js/fr.js"][:fr][:fallback_test].should eql(nil)
268
+ subject[:fr][:fallback_test].should eql(nil)
189
269
  end
190
270
 
191
271
  it "exports with default_locale as fallback when enabled" do
192
272
  set_config "js_file_per_locale_with_fallbacks_enabled.yml"
193
- subject["tmp/i18n-js/fr.js"][:fr][:fallback_test].should eql("Success")
273
+ subject[:fr][:fallback_test].should eql("Success")
194
274
  end
195
275
 
196
276
  it "exports with default_locale as fallback when enabled with :default_locale" do
197
277
  set_config "js_file_per_locale_with_fallbacks_as_default_locale_symbol.yml"
198
- subject["tmp/i18n-js/fr.js"][:fr][:fallback_test].should eql("Success")
278
+ subject[:fr][:fallback_test].should eql("Success")
199
279
  end
200
280
 
201
281
  it "exports with given locale as fallback" do
202
282
  set_config "js_file_per_locale_with_fallbacks_as_locale.yml"
203
- subject["tmp/i18n-js/fr.js"][:fr][:fallback_test].should eql("Erfolg")
283
+ subject[:fr][:fallback_test].should eql("Erfolg")
204
284
  end
205
285
 
206
286
  context "with I18n::Fallbacks enabled" do
@@ -215,17 +295,17 @@ describe I18n::JS do
215
295
 
216
296
  it "exports with defined locale as fallback when enabled" do
217
297
  set_config "js_file_per_locale_with_fallbacks_enabled.yml"
218
- subject["tmp/i18n-js/fr.js"][:fr][:fallback_test].should eql("Erfolg")
298
+ subject[:fr][:fallback_test].should eql("Erfolg")
219
299
  end
220
300
 
221
301
  it "exports with defined locale as fallback when enabled with :default_locale" do
222
302
  set_config "js_file_per_locale_with_fallbacks_as_default_locale_symbol.yml"
223
- subject["tmp/i18n-js/fr.js"][:fr][:fallback_test].should eql("Success")
303
+ subject[:fr][:fallback_test].should eql("Success")
224
304
  end
225
305
 
226
306
  it "exports with Fallbacks as Hash" do
227
307
  set_config "js_file_per_locale_with_fallbacks_as_hash.yml"
228
- subject["tmp/i18n-js/fr.js"][:fr][:fallback_test].should eql("Erfolg")
308
+ subject[:fr][:fallback_test].should eql("Erfolg")
229
309
  end
230
310
  end
231
311
  end
@@ -263,6 +343,7 @@ EOS
263
343
  end
264
344
 
265
345
  context "I18n.available_locales" do
346
+
266
347
  context "when I18n.available_locales is not set" do
267
348
  it "should allow all locales" do
268
349
  result = I18n::JS.scoped_translations("*.admin.*.title")
@@ -401,4 +482,86 @@ EOS
401
482
  end
402
483
  end
403
484
  end
485
+
486
+ describe "translation key sorting" do
487
+
488
+ describe ".sort_translation_keys?" do
489
+ after { described_class.send(:remove_instance_variable, :@sort_translation_keys) }
490
+ subject { described_class.sort_translation_keys? }
491
+
492
+
493
+ context "set with config" do
494
+
495
+ context 'when :sort_translation_keys is not set in config' do
496
+ before :each do
497
+ set_config "default.yml"
498
+ end
499
+
500
+ it { should eq true }
501
+ end
502
+
503
+ context 'when :sort_translation_keys set to true in config' do
504
+ before :each do
505
+ set_config "js_sort_translation_keys_true.yml"
506
+ end
507
+
508
+ it { should eq true }
509
+ end
510
+
511
+ context 'when :sort_translation_keys set to false in config' do
512
+ before :each do
513
+ set_config "js_sort_translation_keys_false.yml"
514
+ end
515
+
516
+ it { should eq false }
517
+ end
518
+ end
519
+
520
+ context 'set by .sort_translation_keys' do
521
+
522
+ context "when it is not set" do
523
+ it { should eq true }
524
+ end
525
+
526
+ context "when it is set to true" do
527
+ before { described_class.sort_translation_keys = true }
528
+
529
+ it { should eq true }
530
+ end
531
+
532
+ context "when it is set to false" do
533
+ before { described_class.sort_translation_keys = false }
534
+
535
+ it { should eq false }
536
+ end
537
+ end
538
+ end
539
+
540
+ context "exporting" do
541
+ subject do
542
+ I18n::JS.export
543
+ file_should_exist "en.js"
544
+ File.read(File.join(I18n::JS.export_i18n_js_dir_path, "en.js"))
545
+ end
546
+
547
+ before do
548
+ stub_const('I18n::JS::DEFAULT_EXPORT_DIR_PATH', temp_path)
549
+ end
550
+
551
+ context 'sort_translation_keys is true' do
552
+ before :each do
553
+ set_config "js_sort_translation_keys_true.yml"
554
+ end
555
+
556
+ it "exports with the keys sorted" do
557
+ expect(subject).to eq(<<EOS
558
+ I18n.translations || (I18n.translations = {});
559
+ I18n.translations["en"] = {"admin":{"edit":{"title":"Edit"},"show":{"note":"more details","title":"Show"}},"date":{"abbr_day_names":["Sun","Mon","Tue","Wed","Thu","Fri","Sat"],"abbr_month_names":[null,"Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"],"day_names":["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"],"formats":{"default":"%Y-%m-%d","long":"%B %d, %Y","short":"%b %d"},"month_names":[null,"January","February","March","April","May","June","July","August","September","October","November","December"]},"fallback_test":"Success","foo":"Foo","number":{"currency":{"format":{"delimiter":",","format":"%u%n","precision":2,"separator":".","unit":"$"}},"format":{"delimiter":",","precision":3,"separator":"."}},"time":{"am":"am","formats":{"default":"%a, %d %b %Y %H:%M:%S %z","long":"%B %d, %Y %H:%M","short":"%d %b %H:%M"},"pm":"pm"}};
560
+ EOS
561
+ )
562
+ end
563
+ end
564
+ end
565
+
566
+ end
404
567
  end
data/spec/segment_spec.rb CHANGED
@@ -3,7 +3,7 @@ require "spec_helper"
3
3
  describe I18n::JS::Segment do
4
4
 
5
5
  let(:file) { "tmp/i18n-js/segment.js" }
6
- let(:translations){ { "en" => { "test" => "Test" }, "fr" => { "test" => "Test2" } } }
6
+ let(:translations){ { en: { "test" => "Test" }, fr: { "test" => "Test2" } } }
7
7
  let(:namespace) { "MyNamespace" }
8
8
  let(:pretty_print){ nil }
9
9
  let(:options) { {namespace: namespace, pretty_print: pretty_print} }
@@ -58,14 +58,52 @@ describe I18n::JS::Segment do
58
58
  before { allow(I18n::JS).to receive(:export_i18n_js_dir_path).and_return(temp_path) }
59
59
  before { subject.save! }
60
60
 
61
- it "should write the file" do
62
- file_should_exist "segment.js"
61
+ context "when file does not include %{locale}" do
62
+ it "should write the file" do
63
+ file_should_exist "segment.js"
63
64
 
64
- File.open(File.join(temp_path, "segment.js")){|f| f.read}.should eql <<-EOF
65
+ File.open(File.join(temp_path, "segment.js")){|f| f.read}.should eql <<-EOF
65
66
  MyNamespace.translations || (MyNamespace.translations = {});
66
67
  MyNamespace.translations["en"] = {"test":"Test"};
67
68
  MyNamespace.translations["fr"] = {"test":"Test2"};
68
- EOF
69
+ EOF
70
+ end
71
+ end
72
+
73
+ context "when file includes %{locale}" do
74
+ let(:file){ "tmp/i18n-js/%{locale}.js" }
75
+
76
+ it "should write files" do
77
+ file_should_exist "en.js"
78
+ file_should_exist "fr.js"
79
+
80
+ File.open(File.join(temp_path, "en.js")){|f| f.read}.should eql <<-EOF
81
+ MyNamespace.translations || (MyNamespace.translations = {});
82
+ MyNamespace.translations["en"] = {"test":"Test"};
83
+ EOF
84
+
85
+ File.open(File.join(temp_path, "fr.js")){|f| f.read}.should eql <<-EOF
86
+ MyNamespace.translations || (MyNamespace.translations = {});
87
+ MyNamespace.translations["fr"] = {"test":"Test2"};
88
+ EOF
89
+ end
90
+ end
91
+
92
+ context "when sort_translation_keys? is true" do
93
+ before :each do
94
+ I18n::JS.sort_translation_keys = true
95
+ end
96
+
97
+ let(:translations){ { en: { "b" => "Test", "a" => "Test" } } }
98
+
99
+ it 'should output the keys as sorted' do
100
+ file_should_exist "segment.js"
101
+
102
+ File.open(File.join(temp_path, "segment.js")){|f| f.read}.should eql <<-EOF
103
+ MyNamespace.translations || (MyNamespace.translations = {});
104
+ MyNamespace.translations["en"] = {"a":"Test","b":"Test"};
105
+ EOF
106
+ end
69
107
  end
70
108
  end
71
109
  end
data/spec/utils_spec.rb CHANGED
@@ -66,4 +66,41 @@ describe I18n::JS::Utils do
66
66
  hash.should eql({:a => {:b => 1, :c => 2}})
67
67
  end
68
68
  end
69
+
70
+ describe ".deep_key_sort" do
71
+ let(:unsorted_hash) { {:z => {:b => 1, :a => 2}, :y => 3} }
72
+ subject(:sorting) { described_class.deep_key_sort(unsorted_hash) }
73
+
74
+ it "performs a deep keys sort without changing the original hash" do
75
+ should eql({:y => 3, :z => {:a => 2, :b => 1}})
76
+ unsorted_hash.should eql({:z => {:b => 1, :a => 2}, :y => 3})
77
+ end
78
+
79
+ # Idea from gem `rails_admin`
80
+ context "when hash contain non-Symbol as key" do
81
+ let(:unsorted_hash) { {:z => {1 => 1, true => 2}, :y => 3} }
82
+
83
+ it "performs a deep keys sort without error" do
84
+ expect{ sorting }.to_not raise_error
85
+ end
86
+ it "converts keys to symbols" do
87
+ should eql({:y => 3, :z => {1 => 1, true => 2}})
88
+ end
89
+ end
90
+ end
91
+
92
+ describe ".scopes_match?" do
93
+ it "performs a comparison of literal scopes" do
94
+ described_class.scopes_match?([:a, :b], [:a, :b, :c]).should_not eql true
95
+ described_class.scopes_match?([:a, :b, :c], [:a, :b, :c]).should eql true
96
+ described_class.scopes_match?([:a, :b, :c], [:a, :b, :d]).should_not eql true
97
+ end
98
+
99
+ it "performs a comparison of wildcard scopes" do
100
+ described_class.scopes_match?([:a, '*'], [:a, :b, :c]).should_not eql true
101
+ described_class.scopes_match?([:a, '*', :c], [:a, :b, :c]).should eql true
102
+ described_class.scopes_match?([:a, :b, :c], [:a, '*', :c]).should eql true
103
+ described_class.scopes_match?([:a, :b, :c], [:a, '*', '*']).should eql true
104
+ end
105
+ end
69
106
  end
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.rc9
4
+ version: 3.0.0.rc10
5
5
  platform: ruby
6
6
  authors:
7
7
  - Nando Vieira
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-03-12 00:00:00.000000000 Z
11
+ date: 2015-05-28 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: i18n
@@ -30,14 +30,14 @@ dependencies:
30
30
  requirements:
31
31
  - - "~>"
32
32
  - !ruby/object:Gem::Version
33
- version: '1.0'
33
+ version: '2.0'
34
34
  type: :development
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
38
  - - "~>"
39
39
  - !ruby/object:Gem::Version
40
- version: '1.0'
40
+ version: '2.0'
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: activesupport
43
43
  requirement: !ruby/object:Gem::Requirement
@@ -141,6 +141,8 @@ files:
141
141
  - spec/fixtures/js_file_per_locale_with_fallbacks_enabled.yml
142
142
  - spec/fixtures/js_file_per_locale_without_fallbacks.yml
143
143
  - spec/fixtures/js_file_with_namespace_and_pretty_print.yml
144
+ - spec/fixtures/js_sort_translation_keys_false.yml
145
+ - spec/fixtures/js_sort_translation_keys_true.yml
144
146
  - spec/fixtures/locales.yml
145
147
  - spec/fixtures/multiple_conditions.yml
146
148
  - spec/fixtures/multiple_conditions_per_locale.yml
@@ -213,6 +215,8 @@ test_files:
213
215
  - spec/fixtures/js_file_per_locale_with_fallbacks_enabled.yml
214
216
  - spec/fixtures/js_file_per_locale_without_fallbacks.yml
215
217
  - spec/fixtures/js_file_with_namespace_and_pretty_print.yml
218
+ - spec/fixtures/js_sort_translation_keys_false.yml
219
+ - spec/fixtures/js_sort_translation_keys_true.yml
216
220
  - spec/fixtures/locales.yml
217
221
  - spec/fixtures/multiple_conditions.yml
218
222
  - spec/fixtures/multiple_conditions_per_locale.yml