i18n-js 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README.rdoc +249 -0
- data/Rakefile +42 -0
- data/init.rb +1 -0
- data/install.rb +7 -0
- data/lib/i18n-js.rb +138 -0
- data/lib/i18n-js/railtie.rb +9 -0
- data/lib/i18n-js/version.rb +10 -0
- data/lib/tasks/i18n-js_tasks.rake +16 -0
- data/source/i18n-js.yml +4 -0
- data/source/i18n.js +341 -0
- data/test/i18n-test.html +50 -0
- data/test/i18n-test.js +668 -0
- data/test/i18n_js_test.rb +168 -0
- data/test/jsunittest/jsunittest.js +1017 -0
- data/test/jsunittest/unittest.css +54 -0
- data/test/resources/custom_path.yml +4 -0
- data/test/resources/default.yml +4 -0
- data/test/resources/locales.yml +76 -0
- data/test/resources/multiple_files.yml +6 -0
- data/test/resources/no_scope.yml +3 -0
- data/test/resources/simple_scope.yml +4 -0
- data/test/test_helper.rb +25 -0
- metadata +84 -0
data/README.rdoc
ADDED
@@ -0,0 +1,249 @@
|
|
1
|
+
= I18n for JavaScript
|
2
|
+
|
3
|
+
It's a small library to provide the Rails I18n translations on the Javascript.
|
4
|
+
|
5
|
+
This library has been tested on:
|
6
|
+
|
7
|
+
* Safari 4 (Mac)
|
8
|
+
* Firefox 3.6 (Mac)
|
9
|
+
* Opera 10 (Mac)
|
10
|
+
* IE6+ (Mac Parallels)
|
11
|
+
|
12
|
+
== Usage
|
13
|
+
|
14
|
+
=== Setting up
|
15
|
+
|
16
|
+
Run <tt>rake i18n:setup</tt> to copy <tt>i18n.js</tt> to your javascript directory and <tt>i18n-js.yml</tt> to your config folder (if not already present). Then you're ready to go!
|
17
|
+
|
18
|
+
Every time your application is started, the translations messages defined in your configuration file will be generated.
|
19
|
+
|
20
|
+
To speed up the development process, you can automatically export your messages by adding something
|
21
|
+
like this to your <tt>ApplicationController</tt>:
|
22
|
+
|
23
|
+
class ApplicationController < ActionController::Base
|
24
|
+
before_filter :export_i18n_messages
|
25
|
+
|
26
|
+
private
|
27
|
+
def export_i18n_messages
|
28
|
+
SimplesIdeias::I18n.export! if Rails.env.development?
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
==== Configuration
|
33
|
+
|
34
|
+
The first time you will restart your application when using i18n-js, it will create you the default configuration file at <tt>#{Rails.root}/config/i18n-js.yml</tt>
|
35
|
+
|
36
|
+
Messages files can also be customized, you can even get more files generated to different folders and with different translations to best suit your needs.
|
37
|
+
|
38
|
+
Examples:
|
39
|
+
|
40
|
+
translations:
|
41
|
+
- file: 'public/javascripts/path-to-your-messages-file.js'
|
42
|
+
only: '*.date.formats'
|
43
|
+
- file: 'public/javascripts/path-to-your-second-file.js'
|
44
|
+
only: ['*.activerecord', '*.admin.*.title']
|
45
|
+
|
46
|
+
If <tt>only</tt> is omitted all the translations will be saved
|
47
|
+
|
48
|
+
To find more examples on how to use the configuration file please refer to the tests.
|
49
|
+
|
50
|
+
=== On the Javascript
|
51
|
+
|
52
|
+
Set your locale is easy as
|
53
|
+
|
54
|
+
I18n.defaultLocale = "pt-BR";
|
55
|
+
I18n.locale = "pt-BR";
|
56
|
+
I18n.currentLocale();
|
57
|
+
// pt-BR
|
58
|
+
|
59
|
+
You can use it to translate your messages:
|
60
|
+
|
61
|
+
I18n.t("some.scoped.translation");
|
62
|
+
|
63
|
+
You can also interpolate values:
|
64
|
+
|
65
|
+
I18n.t("hello", {name: "John Doe"});
|
66
|
+
|
67
|
+
The sample above will assume that you have the following translations in your
|
68
|
+
<tt>config/locales/*.yml</tt>:
|
69
|
+
|
70
|
+
en:
|
71
|
+
hello: "Hello {{name}}!"
|
72
|
+
|
73
|
+
You can set default values for missing scopes:
|
74
|
+
|
75
|
+
// simple translation
|
76
|
+
I18n.t("some.missing.scope", {defaultValue: "A default message"});
|
77
|
+
|
78
|
+
// with interpolation
|
79
|
+
I18n.t("noun", {defaultValue: "I'm a {{noun}}", noun: "Mac"});
|
80
|
+
|
81
|
+
Pluralization is possible as well:
|
82
|
+
|
83
|
+
I18n.t("inbox.counting", {count: 10}); // You have 10 messages
|
84
|
+
|
85
|
+
The sample above expects the following translation:
|
86
|
+
|
87
|
+
en:
|
88
|
+
inbox:
|
89
|
+
counting:
|
90
|
+
one: You have 1 new message
|
91
|
+
other: You have {{count}} new messages
|
92
|
+
zero: You have no messages
|
93
|
+
|
94
|
+
<b>NOTE:</b> Rais I18n recognizes the +zero+ option.
|
95
|
+
|
96
|
+
If you're using the same scope over and over again, you may use the +scope+ option.
|
97
|
+
|
98
|
+
var options = {scope: "activerecord.attributes.user"};
|
99
|
+
|
100
|
+
I18n.t("name", options);
|
101
|
+
I18n.t("email", options);
|
102
|
+
I18n.t("username", options);
|
103
|
+
|
104
|
+
You also provide an array as scope.
|
105
|
+
|
106
|
+
// use the greetings.hello scope
|
107
|
+
I18n.t(["greetings", "hello"]);
|
108
|
+
|
109
|
+
==== Number formatting
|
110
|
+
|
111
|
+
Similar to Rails helpers, you have localize number and currency formatting.
|
112
|
+
|
113
|
+
I18n.l("currency", 1990.99);
|
114
|
+
// $1,990.99
|
115
|
+
|
116
|
+
I18n.l("number", 1990.99);
|
117
|
+
// 1,990.99
|
118
|
+
|
119
|
+
I18n.l("percentage", 123.45);
|
120
|
+
// 123.450%
|
121
|
+
|
122
|
+
To have more control over number formatting, you can use the <tt>I18n.toNumber</tt>, <tt>I18n.toPercentage</tt> and <tt>I18n.toCurrency</tt> functions.
|
123
|
+
|
124
|
+
I18n.toNumber(1000); // 1,000.000
|
125
|
+
I18n.toCurrency(1000); // $1,000.00
|
126
|
+
I18n.toPercentage(100); // 100.000%
|
127
|
+
|
128
|
+
The +toNumber+ and +toPercentage+ functions accept the following options:
|
129
|
+
|
130
|
+
* +precision+: defaults to 3
|
131
|
+
* +separator+: defaults to <tt>.</tt>
|
132
|
+
* +delimiter+: defaults to <tt>,</tt>
|
133
|
+
|
134
|
+
See some number formatting examples:
|
135
|
+
|
136
|
+
I18n.toNumber(1000, {precision: 0}); // 1,000
|
137
|
+
I18n.toNumber(1000, {delimiter: ".", separator: ","}); // 1.000,000
|
138
|
+
I18n.toNumber(1000, {delimiter: ".", precision: 0}); // 1.000
|
139
|
+
|
140
|
+
The +toCurrency+ function accepts the following options:
|
141
|
+
|
142
|
+
* +precision+: sets the level of precision
|
143
|
+
* +separator+: sets the separator between the units
|
144
|
+
* +delimiter+: sets the thousands delimiter
|
145
|
+
* +format+: sets the format of the output string
|
146
|
+
* +unit+: sets the denomination of the currency
|
147
|
+
|
148
|
+
You can provide only the options you want to override:
|
149
|
+
|
150
|
+
I18n.toCurrency(1000, {precision: 0}); /$1,000/
|
151
|
+
|
152
|
+
==== Date formatting
|
153
|
+
|
154
|
+
// accepted formats
|
155
|
+
I18n.l("date.formats.short", "2009-09-18"); // yyyy-mm-dd
|
156
|
+
I18n.l("time.formats.short", "2009-09-18 23:12:43"); // yyyy-mm-dd hh:mm:ss
|
157
|
+
I18n.l("time.formats.short", "2009-11-09T18:10:34"); // JSON format with local Timezone (part of ISO-8601)
|
158
|
+
I18n.l("time.formats.short", "2009-11-09T18:10:34Z"); // JSON format in UTC (part of ISO-8601)
|
159
|
+
I18n.l("date.formats.short", 1251862029000); // Epoch time
|
160
|
+
I18n.l("date.formats.short", "09/18/2009"); // mm/dd/yyyy
|
161
|
+
I18n.l("date.formats.short", (new Date())); // Date object
|
162
|
+
|
163
|
+
If you prefer, you can use the <tt>I18n.strftime</tt> function to format dates.
|
164
|
+
|
165
|
+
var date = new Date();
|
166
|
+
I18n.strftime(date, "%d/%m/%Y");
|
167
|
+
|
168
|
+
The accepted formats are:
|
169
|
+
|
170
|
+
%a - The abbreviated weekday name (Sun)
|
171
|
+
%A - The full weekday name (Sunday)
|
172
|
+
%b - The abbreviated month name (Jan)
|
173
|
+
%B - The full month name (January)
|
174
|
+
%c - The preferred local date and time representation
|
175
|
+
%d - Day of the month (01..31)
|
176
|
+
%-d - Day of the month (1..31)
|
177
|
+
%H - Hour of the day, 24-hour clock (00..23)
|
178
|
+
%-H - Hour of the day, 24-hour clock (0..23)
|
179
|
+
%I - Hour of the day, 12-hour clock (01..12)
|
180
|
+
%-I - Hour of the day, 12-hour clock (1..12)
|
181
|
+
%m - Month of the year (01..12)
|
182
|
+
%-m - Month of the year (1..12)
|
183
|
+
%M - Minute of the hour (00..59)
|
184
|
+
%-M - Minute of the hour (0..59)
|
185
|
+
%p - Meridian indicator (AM or PM)
|
186
|
+
%S - Second of the minute (00..60)
|
187
|
+
%-S - Second of the minute (0..60)
|
188
|
+
%w - Day of the week (Sunday is 0, 0..6)
|
189
|
+
%y - Year without a century (00..99)
|
190
|
+
%-y - Year without a century (0..99)
|
191
|
+
%Y - Year with century
|
192
|
+
%z - Timezone offset (+0545)
|
193
|
+
|
194
|
+
Check out <tt>vendor/plugins/i18n-js/test/i18n-test.js</tt> for more examples!
|
195
|
+
|
196
|
+
== Using I18nJS with other languages (Python, PHP, ...)
|
197
|
+
|
198
|
+
The JavaScript library is language agnostic; so you can use it with PHP, Python, [you favorite language here].
|
199
|
+
The only requirement is that you need to set the +translations+ attribute like following:
|
200
|
+
|
201
|
+
I18n.translations = {};
|
202
|
+
|
203
|
+
I18n.translations["en"] = {
|
204
|
+
message: "Some special message for you"
|
205
|
+
}
|
206
|
+
|
207
|
+
I18n.translations["pt"] = {
|
208
|
+
message: "Uma mensagem especial para você"
|
209
|
+
}
|
210
|
+
|
211
|
+
== Maintainer
|
212
|
+
|
213
|
+
* Nando Vieira - http://simplesideias.com.br
|
214
|
+
* Sébastien Grosjean - http://github.com/ZenCocoon
|
215
|
+
|
216
|
+
== Contributing
|
217
|
+
|
218
|
+
Once you've made your great commits:
|
219
|
+
|
220
|
+
1. Fork[http://help.github.com/forking/] I18n-JS
|
221
|
+
2. Create a topic branch - <tt>git checkout -b my_branch</tt>
|
222
|
+
3. Push to your branch - <tt>git push origin my_branch</tt>
|
223
|
+
4. Create an Issue[http://github.com/fnando/i18n-js/issues] with a link to your branch
|
224
|
+
5. That's it!
|
225
|
+
|
226
|
+
Please respect the indentation rules. And use tabs, not spaces (on the JavaScript).
|
227
|
+
|
228
|
+
== License
|
229
|
+
|
230
|
+
(The MIT License)
|
231
|
+
|
232
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
233
|
+
a copy of this software and associated documentation files (the
|
234
|
+
'Software'), to deal in the Software without restriction, including
|
235
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
236
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
237
|
+
permit persons to whom the Software is furnished to do so, subject to
|
238
|
+
the following conditions:
|
239
|
+
|
240
|
+
The above copyright notice and this permission notice shall be
|
241
|
+
included in all copies or substantial portions of the Software.
|
242
|
+
|
243
|
+
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
|
244
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
245
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
246
|
+
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
247
|
+
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
248
|
+
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
249
|
+
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/Rakefile
ADDED
@@ -0,0 +1,42 @@
|
|
1
|
+
require 'rake'
|
2
|
+
require 'rake/testtask'
|
3
|
+
require 'rake/rdoctask'
|
4
|
+
require 'lib/i18n-js/version'
|
5
|
+
|
6
|
+
desc 'Default: run unit tests.'
|
7
|
+
task :default => :test
|
8
|
+
|
9
|
+
desc 'Test the i18n-js plugin.'
|
10
|
+
Rake::TestTask.new(:test) do |t|
|
11
|
+
t.libs << 'lib'
|
12
|
+
t.libs << 'test'
|
13
|
+
t.pattern = 'test/**/*_test.rb'
|
14
|
+
t.verbose = true
|
15
|
+
end
|
16
|
+
|
17
|
+
desc 'Generate documentation for the i18n-js plugin.'
|
18
|
+
Rake::RDocTask.new(:rdoc) do |rdoc|
|
19
|
+
rdoc.rdoc_dir = 'doc'
|
20
|
+
rdoc.title = 'I18n for JavaScript'
|
21
|
+
rdoc.options << '--line-numbers' << '--inline-source'
|
22
|
+
rdoc.rdoc_files.include('README.rdoc')
|
23
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
24
|
+
end
|
25
|
+
|
26
|
+
begin
|
27
|
+
require 'jeweler'
|
28
|
+
|
29
|
+
JEWEL = Jeweler::Tasks.new do |gem|
|
30
|
+
gem.name = "i18n-js"
|
31
|
+
gem.email = "fnando.vieira@gmail.com"
|
32
|
+
gem.homepage = "http://github.com/fnando/i18n-js"
|
33
|
+
gem.authors = ["Nando Vieira"]
|
34
|
+
gem.version = SimplesIdeias::I18n::Version::STRING
|
35
|
+
gem.summary = "It's a small library to provide the Rails I18n translations on the Javascript."
|
36
|
+
gem.files = FileList["README.rdoc", "init.rb", "install.rb", "{lib,test,source}/**/*", "Rakefile"]
|
37
|
+
end
|
38
|
+
|
39
|
+
Jeweler::GemcutterTasks.new
|
40
|
+
rescue LoadError => e
|
41
|
+
puts "[JEWELER] You can't build a gem until you install jeweler with `gem install jeweler`"
|
42
|
+
end
|
data/init.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "i18n-js"
|
data/install.rb
ADDED
data/lib/i18n-js.rb
ADDED
@@ -0,0 +1,138 @@
|
|
1
|
+
require "FileUtils" unless defined?(FileUtils)
|
2
|
+
|
3
|
+
module SimplesIdeias
|
4
|
+
module I18n
|
5
|
+
extend self
|
6
|
+
|
7
|
+
require "i18n-js/railtie" if Rails.version >= "3.0"
|
8
|
+
|
9
|
+
# deep_merge by Stefan Rusterholz, see http://www.ruby-forum.com/topic/142809
|
10
|
+
MERGER = proc { |key, v1, v2| Hash === v1 && Hash === v2 ? v1.merge(v2, &MERGER) : v2 }
|
11
|
+
|
12
|
+
def config_file
|
13
|
+
Rails.root.join("config/i18n-js.yml")
|
14
|
+
end
|
15
|
+
|
16
|
+
def javascript_file
|
17
|
+
Rails.root.join("public/javascripts/i18n.js")
|
18
|
+
end
|
19
|
+
|
20
|
+
# Export translations to JavaScript, considering settings
|
21
|
+
# from configuration file
|
22
|
+
def export!
|
23
|
+
if config?
|
24
|
+
for options in config[:translations]
|
25
|
+
options.reverse_merge!(:only => "*")
|
26
|
+
|
27
|
+
if options[:only] == "*"
|
28
|
+
save translations, options[:file]
|
29
|
+
else
|
30
|
+
result = scoped_translations(options[:only])
|
31
|
+
save result, options[:file] unless result.empty?
|
32
|
+
end
|
33
|
+
end
|
34
|
+
else
|
35
|
+
save translations, "public/javascripts/translations.js"
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
# Load configuration file for partial exporting and
|
40
|
+
# custom output directory
|
41
|
+
def config
|
42
|
+
HashWithIndifferentAccess.new YAML.load_file(config_file)
|
43
|
+
end
|
44
|
+
|
45
|
+
# Check if configuration file exist
|
46
|
+
def config?
|
47
|
+
File.file? config_file
|
48
|
+
end
|
49
|
+
|
50
|
+
# Copy configuration and JavaScript library files to
|
51
|
+
# <tt>SimplesIdeias::I18n::CONFIG_FILE</tt> and <tt>public/i18n.js</tt>.
|
52
|
+
def setup!
|
53
|
+
FileUtils.cp File.dirname(__FILE__) + "/../source/i18n.js", javascript_file
|
54
|
+
FileUtils.cp(File.dirname(__FILE__) + "/../source/i18n-js.yml", config_file) unless config?
|
55
|
+
end
|
56
|
+
|
57
|
+
# Retrieve an updated JavaScript library from Github.
|
58
|
+
def update!
|
59
|
+
require "open-uri"
|
60
|
+
contents = open("http://github.com/fnando/i18n-js/raw/master/lib/i18n.js").read
|
61
|
+
File.open(javascript_file, "w+") {|f| f << contents}
|
62
|
+
end
|
63
|
+
|
64
|
+
# Convert translations to JSON string and save file.
|
65
|
+
def save(translations, file)
|
66
|
+
file = Rails.root.join(file)
|
67
|
+
FileUtils.mkdir_p File.dirname(file)
|
68
|
+
|
69
|
+
File.open(file, "w+") do |f|
|
70
|
+
f << %(var I18n = I18n || {};\n)
|
71
|
+
f << %(I18n.translations = );
|
72
|
+
f << sorted_hash(translations).to_json
|
73
|
+
f << %(;)
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
def scoped_translations(scopes) # :nodoc:
|
78
|
+
result = {}
|
79
|
+
|
80
|
+
[scopes].flatten.each do |scope|
|
81
|
+
deep_merge! result, filter(translations, scope)
|
82
|
+
end
|
83
|
+
|
84
|
+
result
|
85
|
+
end
|
86
|
+
|
87
|
+
# Filter translations according to the specified scope.
|
88
|
+
def filter(translations, scopes)
|
89
|
+
scopes = scopes.split(".") if scopes.is_a?(String)
|
90
|
+
scopes = scopes.clone
|
91
|
+
scope = scopes.shift
|
92
|
+
|
93
|
+
if scope == "*"
|
94
|
+
results = {}
|
95
|
+
translations.each do |scope, translations|
|
96
|
+
tmp = scopes.empty? ? translations : filter(translations, scopes)
|
97
|
+
results[scope.to_sym] = tmp unless tmp.nil?
|
98
|
+
end
|
99
|
+
return results
|
100
|
+
elsif translations.has_key?(scope.to_sym)
|
101
|
+
return {scope.to_sym => scopes.empty? ? translations[scope.to_sym] : filter(translations[scope.to_sym], scopes)}
|
102
|
+
end
|
103
|
+
nil
|
104
|
+
end
|
105
|
+
|
106
|
+
# Initialize and return translations
|
107
|
+
def translations
|
108
|
+
::I18n.backend.instance_eval do
|
109
|
+
init_translations unless initialized?
|
110
|
+
translations
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
def deep_merge(target, hash) # :nodoc:
|
115
|
+
target.merge(hash, &MERGER)
|
116
|
+
end
|
117
|
+
|
118
|
+
def deep_merge!(target, hash) # :nodoc:
|
119
|
+
target.merge!(hash, &MERGER)
|
120
|
+
end
|
121
|
+
|
122
|
+
# Taken from http://seb.box.re/2010/1/15/deep-hash-ordering-with-ruby-1-8/
|
123
|
+
def sorted_hash(object, deep = false) # :nodoc:
|
124
|
+
if object.is_a?(Hash)
|
125
|
+
res = returning(ActiveSupport::OrderedHash.new) do |map|
|
126
|
+
object.each {|k, v| map[k] = deep ? sorted_hash(v, deep) : v }
|
127
|
+
end
|
128
|
+
return res.class[res.sort {|a, b| a[0].to_s <=> b[0].to_s } ]
|
129
|
+
elsif deep && object.is_a?(Array)
|
130
|
+
array = Array.new
|
131
|
+
object.each_with_index {|v, i| array[i] = sorted_hash(v, deep) }
|
132
|
+
return array
|
133
|
+
else
|
134
|
+
return object
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|