socialcast-i18n-js 4.0.0.rc1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +13 -0
- data/.rspec +1 -0
- data/Gemfile +2 -0
- data/README.md +324 -0
- data/README.rdoc +320 -0
- data/Rakefile +13 -0
- data/app/assets/javascripts/i18n/translations.js.erb +6 -0
- data/app/assets/javascripts/i18n.js +661 -0
- data/config/i18n-js.yml +22 -0
- data/i18n-js.gemspec +28 -0
- data/lib/i18n/js/engine.rb +6 -0
- data/lib/i18n/js/file_dependency_processor.rb +17 -0
- data/lib/i18n/js/railtie.rb +17 -0
- data/lib/i18n/js/translator.rb +56 -0
- data/lib/i18n/js/version.rb +10 -0
- data/lib/i18n-js/engine.rb +63 -0
- data/lib/i18n-js/middleware.rb +59 -0
- data/lib/i18n-js/railtie.rb +13 -0
- data/lib/i18n-js/rake.rb +16 -0
- data/lib/i18n-js/version.rb +10 -0
- data/lib/i18n-js.rb +22 -0
- data/lib/tasks/i18n-js.rake +17 -0
- data/spec/file_dependency_processor_spec.rb +32 -0
- data/spec/fixtures/custom_path.yml +2 -0
- data/spec/fixtures/default.yml +2 -0
- data/spec/fixtures/js_file_per_locale.yml +1 -0
- data/spec/fixtures/locales.yml +76 -0
- data/spec/fixtures/multiple_conditions.yml +3 -0
- data/spec/fixtures/no_config.yml +2 -0
- data/spec/fixtures/simple_scope.yml +2 -0
- data/spec/i18n_js_spec.rb +11 -0
- data/spec/i18n_spec.js +820 -0
- data/spec/i18n_spec.rb +205 -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 +119 -0
- data/spec/js/translations.js +115 -0
- data/spec/resources/custom_path.yml +4 -0
- data/spec/resources/default.yml +4 -0
- data/spec/resources/js_file_per_locale.yml +3 -0
- data/spec/resources/locales.yml +76 -0
- data/spec/resources/multiple_conditions.yml +6 -0
- data/spec/resources/multiple_files.yml +6 -0
- data/spec/resources/no_config.yml +2 -0
- data/spec/resources/no_scope.yml +3 -0
- data/spec/resources/simple_scope.yml +4 -0
- data/spec/spec_helper.rb +40 -0
- data/spec/translator_spec.rb +45 -0
- data/vendor/assets/javascripts/i18n/translations.js.erb +9 -0
- data/vendor/assets/javascripts/i18n.js +531 -0
- data/vendor/assets/javascripts/underscore.js +1059 -0
- metadata +240 -0
@@ -0,0 +1,17 @@
|
|
1
|
+
#encoding: utf-8
|
2
|
+
require 'sprockets'
|
3
|
+
module I18n
|
4
|
+
module Js
|
5
|
+
class FileDependencyProcessor < Sprockets::Processor
|
6
|
+
def self.asset_path_regexp
|
7
|
+
@@asset_path_regexp ||= %r{#{I18n::Js.config[:asset_path]}}
|
8
|
+
end
|
9
|
+
def evaluate(context, locals)
|
10
|
+
return data unless context.logical_path =~ self.class.asset_path_regexp
|
11
|
+
::I18n.load_path.each {|path| context.depend_on(path)}
|
12
|
+
context.depend_on(I18n::Js.config_path)
|
13
|
+
data
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require 'i18n/js/engine'
|
2
|
+
|
3
|
+
module I18n
|
4
|
+
module Js
|
5
|
+
autoload :Translator, "i18n/js/translator"
|
6
|
+
autoload :FileDependencyProcessor, "i18n/js/file_dependency_processor"
|
7
|
+
|
8
|
+
class Railtie < ::Rails::Railtie
|
9
|
+
rake_tasks do
|
10
|
+
load "tasks/i18n-js.rake"
|
11
|
+
end
|
12
|
+
initializer "i18n-js.assetpipeline.environment", :after => "sprockets.environment" do |app|
|
13
|
+
app.assets.register_preprocessor("application/javascript", FileDependencyProcessor) if app.config.assets.enabled
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
module I18n
|
2
|
+
module Js
|
3
|
+
class Translator
|
4
|
+
attr_accessor :scopes, :translations
|
5
|
+
def initialize(scopes)
|
6
|
+
@scopes = scopes
|
7
|
+
@translations = self.class.segment_for_scope(@scopes)
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.segment_for_scope(scope)
|
11
|
+
if scope == "*"
|
12
|
+
translations
|
13
|
+
else
|
14
|
+
scoped_translations(scope)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.scoped_translations(scopes) # :nodoc:
|
19
|
+
result = {}
|
20
|
+
|
21
|
+
[scopes].flatten.each do |scope|
|
22
|
+
result.deep_merge!(filter(translations, scope))
|
23
|
+
end
|
24
|
+
|
25
|
+
result
|
26
|
+
end
|
27
|
+
|
28
|
+
# Filter translations according to the specified scope.
|
29
|
+
def self.filter(filter_translations, scopes)
|
30
|
+
scopes = scopes.split(".") if scopes.is_a?(String)
|
31
|
+
scopes = scopes.clone
|
32
|
+
scope = scopes.shift
|
33
|
+
|
34
|
+
if scope == "*"
|
35
|
+
results = {}
|
36
|
+
filter_translations.each do |filter_scope, filter_translation|
|
37
|
+
tmp = scopes.empty? ? filter_translation : filter(filter_translation, scopes)
|
38
|
+
results[filter_scope.to_sym] = tmp unless tmp.nil?
|
39
|
+
end
|
40
|
+
return results
|
41
|
+
elsif filter_translations.has_key?(scope.to_sym)
|
42
|
+
return {scope.to_sym => scopes.empty? ? filter_translations[scope.to_sym] : filter(filter_translations[scope.to_sym], scopes)}
|
43
|
+
end
|
44
|
+
nil
|
45
|
+
end
|
46
|
+
|
47
|
+
# Initialize and return translations
|
48
|
+
def self.translations
|
49
|
+
::I18n.backend.instance_eval do
|
50
|
+
init_translations unless initialized?
|
51
|
+
translations
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
module SimplesIdeias
|
2
|
+
module I18n
|
3
|
+
class Engine < ::Rails::Engine
|
4
|
+
I18N_TRANSLATIONS_ASSET = "i18n/translations"
|
5
|
+
|
6
|
+
initializer "i18n-js.asset_dependencies", :after => "sprockets.environment",
|
7
|
+
:before => "i18n-js.initialize" do
|
8
|
+
next unless SimplesIdeias::I18n.has_asset_pipeline?
|
9
|
+
|
10
|
+
config = I18n.config_file
|
11
|
+
cache_file = I18n::Engine.load_path_hash_cache
|
12
|
+
|
13
|
+
Rails.application.assets.register_preprocessor "application/javascript", :"i18n-js_dependencies" do |context, data|
|
14
|
+
if context.logical_path == I18N_TRANSLATIONS_ASSET
|
15
|
+
context.depend_on(config) if I18n.config?
|
16
|
+
# also set up dependencies on every locale file
|
17
|
+
::I18n.load_path.each {|path| context.depend_on(path)}
|
18
|
+
|
19
|
+
# Set up a dependency on the contents of the load path
|
20
|
+
# itself. In some situations it is possible to get here
|
21
|
+
# before the path hash cache file has been written; in
|
22
|
+
# this situation, write it now.
|
23
|
+
I18n::Engine.write_hash! unless File.exists?(cache_file)
|
24
|
+
context.depend_on(cache_file)
|
25
|
+
end
|
26
|
+
|
27
|
+
data
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
# rewrite path cache hash at startup and before each request in development
|
32
|
+
config.to_prepare do
|
33
|
+
next unless SimplesIdeias::I18n.has_asset_pipeline?
|
34
|
+
SimplesIdeias::I18n::Engine.write_hash_if_changed unless Rails.env.production?
|
35
|
+
end
|
36
|
+
|
37
|
+
def self.load_path_hash_cache
|
38
|
+
@load_path_hash_cache ||= Rails.root.join("tmp/i18n-js.cache")
|
39
|
+
end
|
40
|
+
|
41
|
+
def self.write_hash_if_changed
|
42
|
+
load_path_hash = ::I18n.load_path.hash
|
43
|
+
|
44
|
+
if load_path_hash != cached_load_path_hash
|
45
|
+
self.cached_load_path_hash = load_path_hash
|
46
|
+
write_hash!
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def self.write_hash!
|
51
|
+
FileUtils.mkdir_p Rails.root.join("tmp")
|
52
|
+
|
53
|
+
File.open(load_path_hash_cache, "w+") do |f|
|
54
|
+
f.write(cached_load_path_hash)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
class << self
|
59
|
+
attr_accessor :cached_load_path_hash
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
module SimplesIdeias
|
2
|
+
module I18n
|
3
|
+
class Middleware
|
4
|
+
def initialize(app)
|
5
|
+
@app = app
|
6
|
+
end
|
7
|
+
|
8
|
+
def call(env)
|
9
|
+
@cache = nil
|
10
|
+
verify_locale_files!
|
11
|
+
@app.call(env)
|
12
|
+
end
|
13
|
+
|
14
|
+
private
|
15
|
+
def cache_path
|
16
|
+
@cache_path ||= Rails.root.join("tmp/cache/i18n-js.yml")
|
17
|
+
end
|
18
|
+
|
19
|
+
def cache
|
20
|
+
@cache ||= begin
|
21
|
+
if cache_path.exist?
|
22
|
+
YAML.load_file(cache_path) || {}
|
23
|
+
else
|
24
|
+
{}
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
# Check if translations should be regenerated.
|
30
|
+
# ONLY REGENERATE when these conditions are met:
|
31
|
+
#
|
32
|
+
# # Cache file doesn't exist
|
33
|
+
# # Translations and cache size are different (files were removed/added)
|
34
|
+
# # Translation file has been updated
|
35
|
+
#
|
36
|
+
def verify_locale_files!
|
37
|
+
valid_cache = []
|
38
|
+
new_cache = {}
|
39
|
+
|
40
|
+
valid_cache.push cache_path.exist?
|
41
|
+
valid_cache.push ::I18n.load_path.uniq.size == cache.size
|
42
|
+
|
43
|
+
::I18n.load_path.each do |path|
|
44
|
+
changed_at = File.mtime(path).to_i
|
45
|
+
valid_cache.push changed_at == cache[path]
|
46
|
+
new_cache[path] = changed_at
|
47
|
+
end
|
48
|
+
|
49
|
+
unless valid_cache.all?
|
50
|
+
File.open(cache_path, "w+") do |file|
|
51
|
+
file << new_cache.to_yaml
|
52
|
+
end
|
53
|
+
|
54
|
+
SimplesIdeias::I18n.export!
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
module SimplesIdeias
|
2
|
+
module I18n
|
3
|
+
class Railtie < Rails::Railtie
|
4
|
+
rake_tasks do
|
5
|
+
require "i18n-js/rake"
|
6
|
+
end
|
7
|
+
|
8
|
+
initializer "i18n-js.initialize" do |app|
|
9
|
+
app.config.middleware.use(Middleware) if Rails.env.development? && !SimplesIdeias::I18n.has_asset_pipeline?
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
data/lib/i18n-js/rake.rb
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
namespace "i18n:js" do
|
2
|
+
desc "Copy i18n.js and configuration file"
|
3
|
+
task :setup => :environment do
|
4
|
+
SimplesIdeias::I18n.setup!
|
5
|
+
end
|
6
|
+
|
7
|
+
desc "Export the messages files"
|
8
|
+
task :export => :environment do
|
9
|
+
SimplesIdeias::I18n.export!
|
10
|
+
end
|
11
|
+
|
12
|
+
desc "Update the JavaScript library"
|
13
|
+
task :update => :environment do
|
14
|
+
SimplesIdeias::I18n.update!
|
15
|
+
end
|
16
|
+
end
|
data/lib/i18n-js.rb
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'active_support/core_ext/hash'
|
2
|
+
|
3
|
+
require 'i18n'
|
4
|
+
require 'i18n/js/railtie' if defined?(Rails)
|
5
|
+
|
6
|
+
module I18n
|
7
|
+
module Js
|
8
|
+
def self.config_path
|
9
|
+
Rails.root.join("config", "i18n-js.yml")
|
10
|
+
end
|
11
|
+
def self.config?
|
12
|
+
File.exists?(config_path)
|
13
|
+
end
|
14
|
+
def self.config
|
15
|
+
@@config ||= if config?
|
16
|
+
YAML.load_file(config_path).with_indifferent_access
|
17
|
+
else
|
18
|
+
{}.with_indifferent_access
|
19
|
+
end.reverse_merge!(:asset_path => "i18n/translation", :only => "*")
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
namespace :i18n do
|
2
|
+
namespace :js do
|
3
|
+
namespace :export do
|
4
|
+
task :assets => [:environment] do
|
5
|
+
require 'fileutils'
|
6
|
+
Dir.glob(Rails.application.assets.paths.map { |path| File.join(path, "#{I18n::Js.config[:asset_path]}*") }) do |asset_path|
|
7
|
+
asset = Rails.application.assets[asset_path]
|
8
|
+
export_path = Rails.root.join('public', asset.logical_path)
|
9
|
+
FileUtils.mkdir_p File.dirname(export_path)
|
10
|
+
File.open(export_path, 'w') do |file|
|
11
|
+
file.write(asset.to_a.map { |x| x.body }.join("\n"))
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
require 'i18n/js/file_dependency_processor'
|
3
|
+
|
4
|
+
describe I18n::Js::FileDependencyProcessor do
|
5
|
+
context "#evaluate" do
|
6
|
+
let(:dependency_processor) { I18n::Js::FileDependencyProcessor.new { @js } }
|
7
|
+
it "sets dependencies on the I18n load_paths and the I18n::Js config file" do
|
8
|
+
::I18n.stub(:load_path => ["/path/to/en.yml", "/path/to/es.yml"])
|
9
|
+
I18n::Js.stub(:config_path => "/path/to/i18n/js/config.yml")
|
10
|
+
context = double("context")
|
11
|
+
context.should_receive(:logical_path).and_return("i18n/translation-en.js")
|
12
|
+
context.should_receive(:depend_on).with("/path/to/en.yml")
|
13
|
+
context.should_receive(:depend_on).with("/path/to/es.yml")
|
14
|
+
context.should_receive(:depend_on).with("/path/to/i18n/js/config.yml")
|
15
|
+
locals = double("locals")
|
16
|
+
dependency_processor.evaluate(context, locals)
|
17
|
+
end
|
18
|
+
it "skips setting dependencies when the logical path doesn't match the asset_path_regexp" do
|
19
|
+
I18n::Js.stub(:config_path => "/path/to/i18n/js/config.yml")
|
20
|
+
context = double("context")
|
21
|
+
context.should_receive(:logical_path).and_return("not_i18n_translation.js")
|
22
|
+
context.should_not_receive(:depend_on)
|
23
|
+
locals = double("locals")
|
24
|
+
dependency_processor.evaluate(context, locals)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
context "#asset_path_regexp" do
|
28
|
+
it "returns a regex of asset_path" do
|
29
|
+
I18n::Js::FileDependencyProcessor.asset_path_regexp.should eql(%r{i18n/translation})
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
only: '*'
|
@@ -0,0 +1,76 @@
|
|
1
|
+
en:
|
2
|
+
number:
|
3
|
+
format:
|
4
|
+
separator: "."
|
5
|
+
delimiter: ","
|
6
|
+
precision: 3
|
7
|
+
currency:
|
8
|
+
format:
|
9
|
+
format: "%u%n"
|
10
|
+
unit: "$"
|
11
|
+
separator: "."
|
12
|
+
delimiter: ","
|
13
|
+
precision: 2
|
14
|
+
date:
|
15
|
+
formats:
|
16
|
+
default: "%Y-%m-%d"
|
17
|
+
short: "%b %d"
|
18
|
+
long: "%B %d, %Y"
|
19
|
+
day_names: [Sunday, Monday, Tuesday, Wednesday, Thursday, Friday, Saturday]
|
20
|
+
abbr_day_names: [Sun, Mon, Tue, Wed, Thu, Fri, Sat]
|
21
|
+
month_names: [~, January, February, March, April, May, June, July, August, September, October, November, December]
|
22
|
+
abbr_month_names: [~, Jan, Feb, Mar, Apr, May, Jun, Jul, Aug, Sep, Oct, Nov, Dec]
|
23
|
+
# order: [ :year, :month, :day ]
|
24
|
+
time:
|
25
|
+
formats:
|
26
|
+
default: "%a, %d %b %Y %H:%M:%S %z"
|
27
|
+
short: "%d %b %H:%M"
|
28
|
+
long: "%B %d, %Y %H:%M"
|
29
|
+
am: "am"
|
30
|
+
pm: "pm"
|
31
|
+
admin:
|
32
|
+
show:
|
33
|
+
title: "Show"
|
34
|
+
note: "more details"
|
35
|
+
edit:
|
36
|
+
title: "Edit"
|
37
|
+
|
38
|
+
fr:
|
39
|
+
date:
|
40
|
+
formats:
|
41
|
+
default: "%d/%m/%Y"
|
42
|
+
short: "%e %b"
|
43
|
+
long: "%e %B %Y"
|
44
|
+
long_ordinal: "%e %B %Y"
|
45
|
+
only_day: "%e"
|
46
|
+
day_names: [dimanche, lundi, mardi, mercredi, jeudi, vendredi, samedi]
|
47
|
+
abbr_day_names: [dim, lun, mar, mer, jeu, ven, sam]
|
48
|
+
month_names: [~, janvier, février, mars, avril, mai, juin, juillet, août, septembre, octobre, novembre, décembre]
|
49
|
+
abbr_month_names: [~, jan., fév., mar., avr., mai, juin, juil., août, sept., oct., nov., déc.]
|
50
|
+
# order: [ :day, :month, :year ]
|
51
|
+
time:
|
52
|
+
formats:
|
53
|
+
default: "%d %B %Y %H:%M"
|
54
|
+
time: "%H:%M"
|
55
|
+
short: "%d %b %H:%M"
|
56
|
+
long: "%A %d %B %Y %H:%M:%S %Z"
|
57
|
+
long_ordinal: "%A %d %B %Y %H:%M:%S %Z"
|
58
|
+
only_second: "%S"
|
59
|
+
am: 'am'
|
60
|
+
pm: 'pm'
|
61
|
+
number:
|
62
|
+
format:
|
63
|
+
precision: 3
|
64
|
+
separator: ','
|
65
|
+
delimiter: ' '
|
66
|
+
currency:
|
67
|
+
format:
|
68
|
+
unit: '€'
|
69
|
+
precision: 2
|
70
|
+
format: '%n %u'
|
71
|
+
admin:
|
72
|
+
show:
|
73
|
+
title: "Visualiser"
|
74
|
+
note: "plus de détails"
|
75
|
+
edit:
|
76
|
+
title: "Editer"
|
@@ -0,0 +1,11 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe I18n::Js do
|
4
|
+
context "general" do
|
5
|
+
it "sets empty hash as configuration when no file is found" do
|
6
|
+
I18n::Js.stub(:config_path => "/path/to/config/config.yml")
|
7
|
+
I18n::Js.config?.should be_false
|
8
|
+
I18n::Js.config.should eql({"asset_path" => "i18n/translation", "only" => "*"})
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|