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 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
@@ -0,0 +1,7 @@
1
+ # Install hook code here
2
+ puts <<-TXT
3
+ Run rake i18n:setup to copy i18n.js to your javascript directory
4
+ and i18n-js.yml to your config folder (if not already present).
5
+
6
+ Then you're ready to go! More details at http://github.com/fnando/i18n-js
7
+ TXT
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
@@ -0,0 +1,9 @@
1
+ module SimplesIdeias
2
+ module I18n
3
+ class Railtie < Rails::Railtie
4
+ rake_tasks do
5
+ load File.dirname(__FILE__) + "/../tasks/i18n-js_tasks.rake"
6
+ end
7
+ end
8
+ end
9
+ end