i18n-js 2.1.2 → 3.0.0.rc1
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile.lock +23 -31
- data/README.md +318 -0
- data/Rakefile +6 -6
- data/app/assets/javascripts/i18n/translations.js.erb +3 -0
- data/i18n-js.gemspec +5 -6
- data/lib/i18n-js.rb +1 -177
- data/lib/i18n.js +698 -0
- data/lib/i18n/js.rb +157 -0
- data/lib/i18n/js/engine.rb +22 -0
- data/lib/{i18n-js → i18n/js}/middleware.rb +7 -7
- data/lib/i18n/js/version.rb +10 -0
- data/spec/{resources → fixtures}/custom_path.yml +1 -1
- data/spec/{resources → fixtures}/default.yml +1 -1
- data/spec/fixtures/js_file_per_locale.yml +3 -0
- data/spec/{resources → fixtures}/locales.yml +1 -1
- data/spec/fixtures/multiple_conditions.yml +5 -0
- data/spec/{resources → fixtures}/multiple_files.yml +3 -3
- data/spec/{resources → fixtures}/no_config.yml +0 -0
- data/spec/{resources → fixtures}/no_scope.yml +1 -1
- data/spec/{resources → fixtures}/simple_scope.yml +1 -1
- data/spec/i18n_js_spec.rb +122 -0
- data/spec/js/currency.spec.js +60 -0
- data/spec/js/current_locale.spec.js +19 -0
- data/spec/js/dates.spec.js +218 -0
- data/spec/js/defaults.spec.js +23 -0
- data/spec/js/interpolation.spec.js +28 -0
- data/spec/js/jasmine/MIT.LICENSE +20 -0
- data/spec/js/jasmine/jasmine-html.js +190 -0
- data/spec/js/jasmine/jasmine.css +166 -0
- data/spec/js/jasmine/jasmine.js +2476 -0
- data/spec/js/jasmine/jasmine_favicon.png +0 -0
- data/spec/js/localization.spec.js +41 -0
- data/spec/js/numbers.spec.js +124 -0
- data/spec/js/placeholder.spec.js +24 -0
- data/spec/js/pluralization.spec.js +105 -0
- data/spec/js/prepare_options.spec.js +41 -0
- data/spec/js/specs.html +46 -0
- data/spec/js/translate.spec.js +115 -0
- data/spec/js/translations.js +115 -0
- data/spec/spec_helper.rb +36 -14
- metadata +115 -69
- data/.gitignore +0 -5
- data/.rspec +0 -1
- data/README.rdoc +0 -305
- data/config/i18n-js.yml +0 -22
- data/lib/i18n-js/engine.rb +0 -62
- data/lib/i18n-js/railtie.rb +0 -13
- data/lib/i18n-js/rake.rb +0 -16
- data/lib/i18n-js/version.rb +0 -10
- data/spec/i18n_spec.js +0 -768
- data/spec/i18n_spec.rb +0 -205
- data/spec/resources/js_file_per_locale.yml +0 -3
- data/spec/resources/multiple_conditions.yml +0 -6
- data/vendor/assets/javascripts/i18n.js +0 -450
- data/vendor/assets/javascripts/i18n/translations.js.erb +0 -7
data/lib/i18n/js.rb
ADDED
@@ -0,0 +1,157 @@
|
|
1
|
+
require "i18n"
|
2
|
+
require "FileUtils" unless defined?(FileUtils)
|
3
|
+
|
4
|
+
module I18n
|
5
|
+
module JS
|
6
|
+
if defined?(Rails)
|
7
|
+
require "i18n/js/middleware"
|
8
|
+
require "i18n/js/engine"
|
9
|
+
end
|
10
|
+
|
11
|
+
# deep_merge by Stefan Rusterholz, see <http://www.ruby-forum.com/topic/142809>.
|
12
|
+
MERGER = proc do |key, v1, v2|
|
13
|
+
Hash === v1 && Hash === v2 ? v1.merge(v2, &MERGER) : v2
|
14
|
+
end
|
15
|
+
|
16
|
+
# Detect if Rails app has asset pipeline support.
|
17
|
+
#
|
18
|
+
def self.has_asset_pipeline?
|
19
|
+
Rails.configuration.respond_to?(:assets) && Rails.configuration.assets.enabled
|
20
|
+
end
|
21
|
+
|
22
|
+
# The configuration file. This defaults to the `config/i18n-js.yml` file.
|
23
|
+
#
|
24
|
+
def self.config_file
|
25
|
+
@config_file ||= "config/i18n-js.yml"
|
26
|
+
end
|
27
|
+
|
28
|
+
# Export translations to JavaScript, considering settings
|
29
|
+
# from configuration file
|
30
|
+
def self.export
|
31
|
+
translation_segments.each do |filename, translations|
|
32
|
+
save(translations, filename)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def self.segments_per_locale(pattern, scope)
|
37
|
+
I18n.available_locales.each_with_object({}) do |locale, segments|
|
38
|
+
result = scoped_translations("#{locale}.#{scope}")
|
39
|
+
next if result.empty?
|
40
|
+
|
41
|
+
segment_name = ::I18n.interpolate(pattern,{:locale => locale})
|
42
|
+
segments[segment_name] = result
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def self.segment_for_scope(scope)
|
47
|
+
if scope == "*"
|
48
|
+
translations
|
49
|
+
else
|
50
|
+
scoped_translations(scope)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def self.configured_segments
|
55
|
+
config[:translations].each_with_object({}) do |options, segments|
|
56
|
+
options.reverse_merge!(:only => "*")
|
57
|
+
if options[:file] =~ ::I18n::INTERPOLATION_PATTERN
|
58
|
+
segments.merge!(segments_per_locale(options[:file], options[:only]))
|
59
|
+
else
|
60
|
+
result = segment_for_scope(options[:only])
|
61
|
+
segments[options[:file]] = result unless result.empty?
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def self.export_dir
|
67
|
+
"public/javascripts"
|
68
|
+
end
|
69
|
+
|
70
|
+
def self.filtered_translations
|
71
|
+
{}.tap do |result|
|
72
|
+
translation_segments.each do |filename, translations|
|
73
|
+
deep_merge!(result, translations)
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
def self.translation_segments
|
79
|
+
if config? && config[:translations]
|
80
|
+
configured_segments
|
81
|
+
else
|
82
|
+
{"#{export_dir}/translations.js" => translations}
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
# Load configuration file for partial exporting and
|
87
|
+
# custom output directory
|
88
|
+
def self.config
|
89
|
+
if config?
|
90
|
+
(YAML.load_file(config_file) || {}).with_indifferent_access
|
91
|
+
else
|
92
|
+
{}
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
# Check if configuration file exist
|
97
|
+
def self.config?
|
98
|
+
File.file? config_file
|
99
|
+
end
|
100
|
+
|
101
|
+
# Convert translations to JSON string and save file.
|
102
|
+
def self.save(translations, file)
|
103
|
+
FileUtils.mkdir_p File.dirname(file)
|
104
|
+
|
105
|
+
File.open(file, "w+") do |f|
|
106
|
+
f << %(I18n.translations = );
|
107
|
+
f << translations.to_json
|
108
|
+
f << %(;)
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
def self.scoped_translations(scopes) # :nodoc:
|
113
|
+
result = {}
|
114
|
+
|
115
|
+
[scopes].flatten.each do |scope|
|
116
|
+
deep_merge! result, filter(translations, scope)
|
117
|
+
end
|
118
|
+
|
119
|
+
result
|
120
|
+
end
|
121
|
+
|
122
|
+
# Filter translations according to the specified scope.
|
123
|
+
def self.filter(translations, scopes)
|
124
|
+
scopes = scopes.split(".") if scopes.is_a?(String)
|
125
|
+
scopes = scopes.clone
|
126
|
+
scope = scopes.shift
|
127
|
+
|
128
|
+
if scope == "*"
|
129
|
+
results = {}
|
130
|
+
translations.each do |scope, translations|
|
131
|
+
tmp = scopes.empty? ? translations : filter(translations, scopes)
|
132
|
+
results[scope.to_sym] = tmp unless tmp.nil?
|
133
|
+
end
|
134
|
+
return results
|
135
|
+
elsif translations.has_key?(scope.to_sym)
|
136
|
+
return {scope.to_sym => scopes.empty? ? translations[scope.to_sym] : filter(translations[scope.to_sym], scopes)}
|
137
|
+
end
|
138
|
+
nil
|
139
|
+
end
|
140
|
+
|
141
|
+
# Initialize and return translations
|
142
|
+
def self.translations
|
143
|
+
::I18n.backend.instance_eval do
|
144
|
+
init_translations unless initialized?
|
145
|
+
translations
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
def self.deep_merge(target, hash) # :nodoc:
|
150
|
+
target.merge(hash, &MERGER)
|
151
|
+
end
|
152
|
+
|
153
|
+
def self.deep_merge!(target, hash) # :nodoc:
|
154
|
+
target.merge!(hash, &MERGER)
|
155
|
+
end
|
156
|
+
end
|
157
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require "i18n/js"
|
2
|
+
|
3
|
+
module I18n
|
4
|
+
module JS
|
5
|
+
class Engine < ::Rails::Engine
|
6
|
+
initializer :after => "sprockets.environment" do
|
7
|
+
path = File.expand_path("../../..", __FILE__)
|
8
|
+
::Rails.configuration.assets.paths.unshift(path) if JS.has_asset_pipeline?
|
9
|
+
end
|
10
|
+
|
11
|
+
ActiveSupport.on_load(:after_initialize, :yield => true) do
|
12
|
+
next unless JS.has_asset_pipeline?
|
13
|
+
|
14
|
+
Rails.application.assets.register_preprocessor "application/javascript", :"i18n-js_dependencies" do |context, source|
|
15
|
+
next source unless context.logical_path == "i18n/translations"
|
16
|
+
::I18n.load_path.each {|path| context.depend_on(path)}
|
17
|
+
source
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -1,5 +1,5 @@
|
|
1
|
-
module
|
2
|
-
module
|
1
|
+
module I18n
|
2
|
+
module JS
|
3
3
|
class Middleware
|
4
4
|
def initialize(app)
|
5
5
|
@app = app
|
@@ -46,13 +46,13 @@ module SimplesIdeias
|
|
46
46
|
new_cache[path] = changed_at
|
47
47
|
end
|
48
48
|
|
49
|
-
|
50
|
-
File.open(cache_path, "w+") do |file|
|
51
|
-
file << new_cache.to_yaml
|
52
|
-
end
|
49
|
+
return if valid_cache.all?
|
53
50
|
|
54
|
-
|
51
|
+
File.open(cache_path, "w+") do |file|
|
52
|
+
file << new_cache.to_yaml
|
55
53
|
end
|
54
|
+
|
55
|
+
::I18n::JS.export
|
56
56
|
end
|
57
57
|
end
|
58
58
|
end
|
@@ -1,6 +1,6 @@
|
|
1
1
|
# Find more details about this configuration file at http://github.com/fnando/i18n-js
|
2
2
|
translations:
|
3
|
-
- file: "
|
3
|
+
- file: "tmp/i18n-js/all.js"
|
4
|
+
only: "*"
|
5
|
+
- file: "tmp/i18n-js/tudo.js"
|
4
6
|
only: "*"
|
5
|
-
- file: "public/javascripts/tudo.js"
|
6
|
-
only: "*"
|
File without changes
|
@@ -0,0 +1,122 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe I18n::JS do
|
4
|
+
context "exporting" do
|
5
|
+
before do
|
6
|
+
I18n::JS.stub :export_dir => temp_path
|
7
|
+
end
|
8
|
+
|
9
|
+
it "exports messages to default path when configuration file doesn't exist" do
|
10
|
+
I18n::JS.export
|
11
|
+
file_should_exist "translations.js"
|
12
|
+
end
|
13
|
+
|
14
|
+
it "exports messages using custom output path" do
|
15
|
+
set_config "custom_path.yml"
|
16
|
+
I18n::JS.should_receive(:save).with(translations, "tmp/i18n-js/all.js")
|
17
|
+
I18n::JS.export
|
18
|
+
end
|
19
|
+
|
20
|
+
it "sets default scope to * when not specified" do
|
21
|
+
set_config "no_scope.yml"
|
22
|
+
I18n::JS.should_receive(:save).with(translations, "tmp/i18n-js/no_scope.js")
|
23
|
+
I18n::JS.export
|
24
|
+
end
|
25
|
+
|
26
|
+
it "exports to multiple files" do
|
27
|
+
set_config "multiple_files.yml"
|
28
|
+
I18n::JS.export
|
29
|
+
|
30
|
+
file_should_exist "all.js"
|
31
|
+
file_should_exist "tudo.js"
|
32
|
+
end
|
33
|
+
|
34
|
+
it "ignores an empty config file" do
|
35
|
+
set_config "no_config.yml"
|
36
|
+
I18n::JS.export
|
37
|
+
|
38
|
+
file_should_exist "translations.js"
|
39
|
+
end
|
40
|
+
|
41
|
+
it "exports to a JS file per available locale" do
|
42
|
+
set_config "js_file_per_locale.yml"
|
43
|
+
I18n::JS.export
|
44
|
+
|
45
|
+
file_should_exist "en.js"
|
46
|
+
end
|
47
|
+
|
48
|
+
it "exports with multiple conditions" do
|
49
|
+
set_config "multiple_conditions.yml"
|
50
|
+
I18n::JS.export
|
51
|
+
|
52
|
+
file_should_exist "bitsnpieces.js"
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
context "filters" do
|
57
|
+
it "filters translations using scope *.date.formats" do
|
58
|
+
result = I18n::JS.filter(translations, "*.date.formats")
|
59
|
+
result[:en][:date].keys.should eql([:formats])
|
60
|
+
result[:fr][:date].keys.should eql([:formats])
|
61
|
+
end
|
62
|
+
|
63
|
+
it "filters translations using scope [*.date.formats, *.number.currency.format]" do
|
64
|
+
result = I18n::JS.scoped_translations(["*.date.formats", "*.number.currency.format"])
|
65
|
+
result[:en].keys.collect(&:to_s).sort.should eql(%w[ date number ])
|
66
|
+
result[:fr].keys.collect(&:to_s).sort.should eql(%w[ date number ])
|
67
|
+
end
|
68
|
+
|
69
|
+
it "filters translations using multi-star scope" do
|
70
|
+
result = I18n::JS.scoped_translations("*.*.formats")
|
71
|
+
|
72
|
+
result[:en].keys.collect(&:to_s).sort.should eql(%w[ date time ])
|
73
|
+
result[:fr].keys.collect(&:to_s).sort.should eql(%w[ date time ])
|
74
|
+
|
75
|
+
result[:en][:date].keys.should eql([:formats])
|
76
|
+
result[:en][:time].keys.should eql([:formats])
|
77
|
+
|
78
|
+
result[:fr][:date].keys.should eql([:formats])
|
79
|
+
result[:fr][:time].keys.should eql([:formats])
|
80
|
+
end
|
81
|
+
|
82
|
+
it "filters translations using alternated stars" do
|
83
|
+
result = I18n::JS.scoped_translations("*.admin.*.title")
|
84
|
+
|
85
|
+
result[:en][:admin].keys.collect(&:to_s).sort.should eql(%w[ edit show ])
|
86
|
+
result[:fr][:admin].keys.collect(&:to_s).sort.should eql(%w[ edit show ])
|
87
|
+
|
88
|
+
result[:en][:admin][:show][:title].should eql("Show")
|
89
|
+
result[:fr][:admin][:show][:title].should eql("Visualiser")
|
90
|
+
|
91
|
+
result[:en][:admin][:edit][:title].should eql("Edit")
|
92
|
+
result[:fr][:admin][:edit][:title].should eql("Editer")
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
context "general" do
|
97
|
+
it "sets export directory" do
|
98
|
+
I18n::JS.export_dir.should eql("public/javascripts")
|
99
|
+
end
|
100
|
+
|
101
|
+
it "sets empty hash as configuration when no file is found" do
|
102
|
+
I18n::JS.config?.should be_false
|
103
|
+
I18n::JS.config.should eql({})
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
context "hash merging" do
|
108
|
+
it "performs a deep merge" do
|
109
|
+
target = {:a => {:b => 1}}
|
110
|
+
result = I18n::JS.deep_merge(target, {:a => {:c => 2}})
|
111
|
+
|
112
|
+
result[:a].should eql({:b => 1, :c => 2})
|
113
|
+
end
|
114
|
+
|
115
|
+
it "performs a banged deep merge" do
|
116
|
+
target = {:a => {:b => 1}}
|
117
|
+
I18n::JS.deep_merge!(target, {:a => {:c => 2}})
|
118
|
+
|
119
|
+
target[:a].should eql({:b => 1, :c => 2})
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
var I18n = require("../../lib/i18n")
|
2
|
+
, Translations = require("./translations")
|
3
|
+
;
|
4
|
+
|
5
|
+
describe("Currency", function(){
|
6
|
+
var actual, expected;
|
7
|
+
|
8
|
+
beforeEach(function() {
|
9
|
+
I18n.reset();
|
10
|
+
I18n.translations = Translations();
|
11
|
+
});
|
12
|
+
|
13
|
+
it("formats currency with default settings", function(){
|
14
|
+
expect(I18n.toCurrency(100.99)).toEqual("$100.99");
|
15
|
+
expect(I18n.toCurrency(1000.99)).toEqual("$1,000.99");
|
16
|
+
});
|
17
|
+
|
18
|
+
it("formats currency with custom settings", function(){
|
19
|
+
I18n.translations.en.number = {
|
20
|
+
currency: {
|
21
|
+
format: {
|
22
|
+
format: "%n %u",
|
23
|
+
unit: "USD",
|
24
|
+
delimiter: ".",
|
25
|
+
separator: ",",
|
26
|
+
precision: 2
|
27
|
+
}
|
28
|
+
}
|
29
|
+
};
|
30
|
+
|
31
|
+
expect(I18n.toCurrency(12)).toEqual("12,00 USD");
|
32
|
+
expect(I18n.toCurrency(123)).toEqual("123,00 USD");
|
33
|
+
expect(I18n.toCurrency(1234.56)).toEqual("1.234,56 USD");
|
34
|
+
});
|
35
|
+
|
36
|
+
it("formats currency with custom settings and partial overriding", function(){
|
37
|
+
I18n.translations.en.number = {
|
38
|
+
currency: {
|
39
|
+
format: {
|
40
|
+
format: "%n %u",
|
41
|
+
unit: "USD",
|
42
|
+
delimiter: ".",
|
43
|
+
separator: ",",
|
44
|
+
precision: 2
|
45
|
+
}
|
46
|
+
}
|
47
|
+
};
|
48
|
+
|
49
|
+
expect(I18n.toCurrency(12, {precision: 0})).toEqual("12 USD");
|
50
|
+
expect(I18n.toCurrency(123, {unit: "bucks"})).toEqual("123,00 bucks");
|
51
|
+
});
|
52
|
+
|
53
|
+
it("formats currency with some custom options that should be merged with default options", function(){
|
54
|
+
expect(I18n.toCurrency(1234, {precision: 0})).toEqual("$1,234");
|
55
|
+
expect(I18n.toCurrency(1234, {unit: "º"})).toEqual("º1,234.00");
|
56
|
+
expect(I18n.toCurrency(1234, {separator: "-"})).toEqual("$1,234-00");
|
57
|
+
expect(I18n.toCurrency(1234, {delimiter: "-"})).toEqual("$1-234.00");
|
58
|
+
expect(I18n.toCurrency(1234, {format: "%u %n"})).toEqual("$ 1,234.00");
|
59
|
+
});
|
60
|
+
});
|