i18n-js 3.0.0.rc9 → 3.0.0.rc10

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