roda-i18n 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 5a3d6733ef6fdf42c06e7e28107e123ba746f0af
4
+ data.tar.gz: 517043eb03472e935a0ca625bd71cc07c53a8fe9
5
+ SHA512:
6
+ metadata.gz: 33080f5390f7fea5ec9b4f6c85fb405cbaf70d0b8ce187eca20917ad83d61b071140deb5b03c04653a04f3fcb5f3d9816219f04a808a8ef3bec618e381e801b9
7
+ data.tar.gz: 1e96099edd2e3ecc6868b67982edb185a9ce9ed5cf5cb9dacb0d63c1f12e52fc3328653a3320b775189ceeef3d26f0341204b05ca9607d2d4c1a4c00603c93cd
@@ -0,0 +1,18 @@
1
+ Copyright (c) 2015 Kematzy
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ of this software and associated documentation files (the "Software"), to
5
+ deal in the Software without restriction, including without limitation the
6
+ rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
7
+ sell copies of the Software, and to permit persons to whom the Software is
8
+ furnished to do so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in
11
+ all copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
16
+ THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
17
+ IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
18
+ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,309 @@
1
+ # Roda-i18n
2
+
3
+ Easily add Internationalisation (i18n) and localisation support for [Roda](http://roda.jeremyevans.net/) apps, based upon the [R18n](https://github.com/ai/r18n) gem.
4
+
5
+ Extensively tested and with 100% code test coverage.
6
+
7
+
8
+ ## Installation
9
+
10
+ To use this gem, just do
11
+
12
+ $ (sudo) gem install roda-i18n
13
+
14
+ or if you use Bundler
15
+
16
+ gem "roda-i18n"
17
+
18
+
19
+ <br>
20
+
21
+
22
+ ## Getting Started
23
+
24
+ To add internationalisation and localisation support to your app just add the following code snippet in your app.
25
+
26
+ plugin :i18n
27
+
28
+ By default the default locale is set to <tt>'en'</tt> and the translations directory is set to the <tt>'i18n'</tt> directory in the rooot of your app, ie: '<tt>/path/2/app/i18n</tt>'.
29
+
30
+
31
+ **IMPORTANT! Make sure you create the 'i18n' folder and add an 'en.yml' file with
32
+ at least one translation within it.**
33
+
34
+
35
+ <br>
36
+
37
+
38
+ ## Configuration
39
+
40
+ ### Overriding defaults during configuration
41
+
42
+ Both <tt>:locale</tt> and <tt>:translations</tt> can be configured (overridden) during plugin configuration:
43
+
44
+ plugin :i18n, :locale => ['de'], :translations => ['absolute/path/2/i18n']
45
+
46
+
47
+ **NOTE!**
48
+
49
+ 1. You must set <tt>opts[:root]</tt> in your app if you do not define the <tt>:translations</tt> path during plugin configuration.
50
+
51
+ 2. When overriding <tt>:translations</tt> the **any path(s) given must be absolute**.
52
+
53
+
54
+ #### Loading translations from multiple i18n directories
55
+
56
+ The <tt>:translations</tt> path supports 'wildcards', ie: <tt>path/**/i18n</tt> so you can load translations from multiple combined apps, each with their own <tt>i18n</tt> folder with translations.
57
+
58
+ **Please Note!**
59
+
60
+ When loading translations from multiple sources and the same translation key is available in multiple files of the same locale, then **the translations in the first loaded translation file takes precedence over subsequent loaded translations**.
61
+
62
+ * ie: translations in <tt>./i18n/en.yml</tt> takes precedence over translations in <tt>./apps/app1/i18n/en.yml</tt>
63
+
64
+
65
+ You can also set a list of preferred locales as an array ordered by priority.
66
+
67
+ plugin :i18n, :locale => ['es','fr','en']
68
+
69
+
70
+
71
+ <br>
72
+
73
+
74
+ ## USAGE
75
+
76
+ The **i18n** plugin depends upon simple YAML based translations files:
77
+
78
+ # app/i18n/en.yml
79
+
80
+ user:
81
+ edit: Edit user
82
+ name: User name is %1
83
+ count: !!pl
84
+ 1: There is 1 user
85
+ n: There are %1 users
86
+
87
+
88
+ ...and the **<tt>:t</tt>** instance method to output the translations:
89
+
90
+
91
+ t.user.edit #=> "Edit user"
92
+ t.user.name('John') #=> "User name is John"
93
+ t.user.count(5) #=> "There are 5 users"
94
+
95
+ t.does.not.exist | 'default' #=> "default"
96
+
97
+
98
+ ...and the **<tt>:l</tt>** (lowercase L) instance method provides built-in localisations support:
99
+
100
+
101
+ l Time.now #=> "03/01/2010 18:54"
102
+ l Time.now, :human #=> "now"
103
+ l Time.now, :full #=> "3rd of January, 2010 18:54"
104
+
105
+
106
+
107
+ Both the <tt>:t</tt> and <tt>:l</tt> methods are available within the route and template (erb) scopes. ie:
108
+
109
+
110
+ route do |r|
111
+ r.root do
112
+ t.welcome.message
113
+ end
114
+ end
115
+
116
+ # app/views/layout.erb
117
+ <snip...>
118
+ <h1><%= t.welcome.message %></h1>
119
+ <snip...>
120
+
121
+
122
+
123
+ Please visit [R18n](https://github.com/ai/r18n/tree/master/r18n-core) for more information about the R18n gem used to create the above.
124
+
125
+ <br>
126
+
127
+ ## Key Methods / Functionality
128
+
129
+
130
+ <br>
131
+
132
+
133
+ ### <tt>#locale(opts={},&blk)</tt> - (request method)
134
+
135
+ This request method makes it easy to handle translations based upon the **<tt>:locale</tt> prefix on a route / URL**. ie: <tt>blog.com/**de**/posts</tt>.
136
+
137
+
138
+ To enable this, just use the following code structure:
139
+
140
+ route do |r|
141
+
142
+ # all routes are prefixed with '/:locale'
143
+ # ie: GET /de/posts => will use DE translations
144
+ # ie: GET /es/posts => will use ES translations
145
+
146
+ r.locale do # also aliased as #i18n_locale
147
+ r.is 'posts' do
148
+ t.posts.header # use translations or locales
149
+ end
150
+ end
151
+
152
+ end
153
+
154
+
155
+
156
+
157
+ **NOTE!** Any URL / request with a non-present or not supported locale will be given the **configured default locale** or the EN (English) default locale.
158
+
159
+
160
+ <br>
161
+
162
+
163
+ ### <tt>#i18n_set_locale_from(type)</tt> - (request method)
164
+
165
+ Obtains the locale from either ENV, HTTP (browser), Params or Session values.
166
+
167
+ route do |r|
168
+ # A): set from session[:locale] (if present)
169
+ r.i18n_set_locale_from(:session)
170
+
171
+ # B): set from URL params ie: GET /posts?locale=de
172
+ r.i18n_set_locale_from(:params)
173
+
174
+ # C): set from the browser's HTTP request locale
175
+ r.i18n_set_locale_from(:http)
176
+
177
+ # D): set from the server ENV['LANG'] variable
178
+ r.i18n_set_locale_from(:ENV)
179
+
180
+
181
+ r.is 'posts' do
182
+ t.posts.header # use translations
183
+ end
184
+ end
185
+
186
+
187
+ **NOTE!** defaults to the configured default locale, or English, if the given locale type is invalid.
188
+
189
+
190
+ <br>
191
+
192
+
193
+ ### <tt>#i18n_set_locale(locale, &blk)</tt> - (request method)
194
+
195
+ Enables overriding the default locale and setting a temporary :locale
196
+ within a route block.
197
+
198
+ route do |r|
199
+ # configured default locale
200
+
201
+ <snip...>
202
+
203
+ r.i18n_set_locale('de') do
204
+ # within this route block the locale is DE (German)
205
+ end
206
+
207
+ r.i18n_set_locale('es') do
208
+ # within this route block the locale is ES (Spanish)
209
+ end
210
+
211
+ end
212
+
213
+
214
+
215
+
216
+
217
+ ## InstanceMethods
218
+
219
+ ### `#t`
220
+
221
+ This is the main translation output method. (See examples above)
222
+
223
+
224
+ <br>
225
+
226
+
227
+ ### `#l`
228
+
229
+ Key localisation method. Handles dates etc. (See examples above)
230
+
231
+
232
+ <br>
233
+
234
+
235
+ ### `#i18n_available_locales`
236
+
237
+ Returns a two-dimensional array of available locales.
238
+
239
+ puts i18n_available_locales #=> [ ['en', 'English'], ...]
240
+
241
+
242
+
243
+
244
+ ### `#i18n_default_places`
245
+
246
+
247
+ <br>
248
+
249
+
250
+ ## Class Methods
251
+
252
+ ### <tt>#i18n_opts</tt>
253
+
254
+ Return the i18n options for this class as a Hash.
255
+
256
+
257
+ <br>
258
+
259
+
260
+ ## Ideas
261
+
262
+ A few ideas that may be outlandish, but possible?
263
+
264
+
265
+ ### Ability to load translations from multiple locations via an array.
266
+
267
+ plugin :i18n, :translations => ['app1/i18n', 'app2/i18n', 'app3/i18n']
268
+
269
+
270
+ > [Concept Reference](https://github.com/ai/r18n/tree/master/r18n-core#loaders)
271
+ >
272
+ > You can also set several loaders to merge translations from different sources:
273
+ >
274
+ > R18n.default_places = [MyLoader.new, DBLoader.new, 'path/to/yaml']
275
+ >
276
+
277
+
278
+ ### Sequel DBLoader for DB based translations support
279
+
280
+ Som form of built-in support for storing / loading translations from a Sequel based DB.
281
+
282
+
283
+ <br>
284
+
285
+
286
+ ----
287
+
288
+ ## TODOs
289
+
290
+ * I'm sure there's something, but right now the list is empty ;-)
291
+
292
+
293
+ ## Credits
294
+
295
+ * This plugin have been inspired by the `sinatra-i18n` gem available at [github/ai/r18n](http://github.com/ai/r18n) created by [Andrey Sitnik](http://github.com/ai).
296
+
297
+ * Testing code have been partly copied from [Forme](github.com/jeremyevans/forme).
298
+
299
+ * Inspiration and assistance by [Jeremy Evans](github.com/jeremyevans). Many thanks Jeremy!!
300
+
301
+
302
+ ## Licence
303
+
304
+ MIT
305
+
306
+ Copyright: 2015 Kematzy
307
+
308
+
309
+
@@ -0,0 +1,78 @@
1
+ require "rake"
2
+ require "rake/clean"
3
+
4
+ NAME = 'roda-i18n'
5
+ CLEAN.include ["#{NAME}-*.gem", "rdoc", "coverage", '**/*.rbc']
6
+
7
+ # Gem Packaging and Release
8
+
9
+ desc "Packages #{NAME}"
10
+ task :package=>[:clean] do |p|
11
+ sh %{gem build #{NAME}.gemspec}
12
+ end
13
+
14
+ desc "Upload #{NAME} gem to rubygems"
15
+ task :release=>[:package] do
16
+ sh %{gem push ./#{NAME}-#{VERS.call}.gem}
17
+ end
18
+
19
+ # ### RDoc
20
+ #
21
+ # RDOC_DEFAULT_OPTS = ["--line-numbers", "--inline-source", '--title', 'Roda I18n']
22
+ #
23
+ # begin
24
+ # gem 'hanna-nouveau'
25
+ # RDOC_DEFAULT_OPTS.concat(['-f', 'hanna'])
26
+ # rescue Gem::LoadError
27
+ # end
28
+ #
29
+ # rdoc_task_class = begin
30
+ # require "rdoc/task"
31
+ # RDoc::Task
32
+ # rescue LoadError
33
+ # require "rake/rdoctask"
34
+ # Rake::RDocTask
35
+ # end
36
+ #
37
+ # RDOC_OPTS = RDOC_DEFAULT_OPTS + ['--main', 'README.rdoc']
38
+ #
39
+ # rdoc_task_class.new do |rdoc|
40
+ # rdoc.rdoc_dir = "rdoc"
41
+ # rdoc.options += RDOC_OPTS
42
+ # rdoc.rdoc_files.add %w"README.rdoc CHANGELOG MIT-LICENSE lib/**/*.rb"
43
+ # end
44
+
45
+ ### Specs
46
+
47
+ desc "Run specs"
48
+ task :spec do
49
+ sh "#{FileUtils::RUBY} -rubygems -I lib -e 'ARGV.each{|f| require f}' ./spec/*_spec.rb"
50
+ end
51
+ task :default => :spec
52
+
53
+ desc "Run specs with coverage"
54
+ task :coverage do
55
+ ENV['COVERAGE'] = '1'
56
+ Rake::Task['spec'].invoke
57
+ end
58
+
59
+ ### Other
60
+
61
+ # desc "Print #{NAME} version"
62
+ # task :version do
63
+ # puts VERS.call
64
+ # end
65
+
66
+ desc "Check syntax of all .rb files"
67
+ task :check_syntax do
68
+ Dir['**/*.rb'].each{|file| print `#{ENV['RUBY'] || :ruby} -c #{file} | fgrep -v "Syntax OK"`}
69
+ end
70
+
71
+ # desc "Start an IRB shell using the extension"
72
+ # task :irb do
73
+ # require 'rbconfig'
74
+ # ruby = ENV['RUBY'] || File.join(RbConfig::CONFIG['bindir'], RbConfig::CONFIG['ruby_install_name'])
75
+ # irb = ENV['IRB'] || File.join(RbConfig::CONFIG['bindir'], File.basename(ruby).sub('ruby', 'irb'))
76
+ # sh %{#{irb} -I lib -r forme}
77
+ # end
78
+
@@ -0,0 +1,276 @@
1
+ require 'r18n-core'
2
+
3
+ class Roda
4
+ module RodaPlugins
5
+ # The i18n plugin allows you to easily add internationalisation (i18n) and
6
+ # localisation support to your Roda app, by adding the following:
7
+ #
8
+ # plugin :i18n
9
+ #
10
+ # By default the default locale is set to <tt>'en'</tt> and the translations directory
11
+ # is set to <tt>'i18n'</tt> in the rooot of your app.
12
+ #
13
+ # Both <tt>:locale</tt> and <tt>:translations</tt> can be overridden during configuration:
14
+ #
15
+ # plugin :i18n, :locale => ['de'], :translations => ['absolute/path/2/i18n']
16
+ #
17
+ # Please note!
18
+ # 1) You must set <tt>opts[:root]</tt> in your app if you don't define the <tt>:translations</tt> path.
19
+ #
20
+ # 2) When overriding <tt>:translations</tt> the path given must be absolute.
21
+ #
22
+ # The path supports 'wildcards', ie: path/**/i18n so you can load translations from multiple
23
+ # combined apps each with their own <tt>i18n</tt> folder with translations.
24
+ #
25
+ # Note! when loading translations from multiple sources and the same translation key is used
26
+ # in both files, the first loaded file takes precedence, ie: <tt>./i18n/en.yml</tt> takes precedence over
27
+ # <tt>./apps/app1/i18n/en.yml</tt>
28
+ #
29
+ # == USAGE
30
+ #
31
+ # The i18n plugin depends upon simple YAML based translations files:
32
+ #
33
+ # # app/i18n/en.yml
34
+ #
35
+ # user:
36
+ # edit: Edit user
37
+ # name: User name is %1
38
+ # count: !!pl
39
+ # 1: There is 1 user
40
+ # n: There are %1 users
41
+ #
42
+ #
43
+ # and the <tt>:t</tt> instance method to output the translations:
44
+ #
45
+ #
46
+ # t.user.edit #=> "Edit user"
47
+ # t.user.name('John') #=> "User name is John"
48
+ # t.user.count(5) #=> "There are 5 users"
49
+ #
50
+ # t.does.not.exist | 'default' #=> "default"
51
+ #
52
+ #
53
+ # the <tt>:l</tt> instance method provides built-in localisations support:
54
+ #
55
+ # l Time.now #=> "03/01/2010 18:54"
56
+ # l Time.now, :human #=> "now"
57
+ # l Time.now, :full #=> "3rd of January, 2010 18:54"
58
+ #
59
+ # Both the <tt>:t</tt> and <tt>:l</tt> methods are available in the route and template (erb) scopes. ie:
60
+ #
61
+ # route do |r|
62
+ # r.root do
63
+ # t.welcome.message
64
+ # end
65
+ # end
66
+ #
67
+ # # app/views/layout.erb
68
+ # <snip...>
69
+ # <h1><%= t.welcome.message %></h1>
70
+ # <snip...>
71
+ #
72
+ #
73
+ #
74
+ # Visit [R18n](https://github.com/ai/r18n/tree/master/r18n-core) for more information.
75
+ #
76
+ #
77
+ # The i18n plugin also makes it easy to handle locales:
78
+ #
79
+ #
80
+ # === <tt>:locale</tt> RequestMethod
81
+ #
82
+ # This request method makes it to handle translations based upon the :locale prefix on a URL,
83
+ # ie: <tt>blog.com/de/posts</tt>, just use the following code:
84
+ #
85
+ # route do |r|
86
+ #
87
+ # r.locale do
88
+ # r.is 'posts' do
89
+ # t.posts.header
90
+ # end
91
+ # end
92
+ #
93
+ # end
94
+ #
95
+ #
96
+ # === <tt>:i18n_set_locale_from</tt> RequestMethod
97
+ #
98
+ # Obtains the locale from either ENV, HTTP (browser), Params or Session
99
+ # values
100
+ #
101
+ #
102
+ # Naturally we can allow browsers to override the default locale within routes, like this:
103
+ #
104
+ # route do |r|
105
+ # i18n_set_locale_from(:http) #=> set to the browser's default locale (en-US)
106
+ # r.get '' do
107
+ # t.hello #=> 'Howdy, I speak American English'
108
+ # end
109
+ # end
110
+ #
111
+ # The def
112
+ #
113
+ #
114
+ # route do |r|
115
+ # i18n_set_locale('de')
116
+ # r.get 'in-german' do
117
+ # t.hello #=> 'Guten tag, ich spreche deutsch'
118
+ # end
119
+ # end
120
+ #
121
+ #
122
+ #
123
+ #
124
+ module RodaI18n
125
+
126
+ def self.load_dependencies(app, opts={})
127
+ # app.plugin :render
128
+ # app.plugin :environments
129
+ end
130
+
131
+ def self.configure(app, opts={})
132
+ opts = app.opts[:i18n][:orig_opts].merge(opts) if app.opts[:i18n]
133
+ app.opts[:i18n] = opts.dup
134
+ app.opts[:i18n][:orig_opts] = opts
135
+ opts = app.opts[:i18n]
136
+
137
+ ::R18n.default_places = opts[:translations] || File.expand_path('i18n', app.opts[:root])
138
+
139
+ opts[:default_locale] = opts[:locale].is_a?(Array) ? opts[:locale].first : opts[:locale] || 'en'
140
+ ::R18n::I18n.default = opts[:default_locale]
141
+
142
+ ::R18n.clear_cache! if ENV['RACK_ENV'] != 'production'
143
+ i18n = R18n::I18n.new(opts[:locale], ::R18n.default_places,
144
+ off_filters: :untranslated,
145
+ on_filters: :untranslated_html)
146
+ ::R18n.set(i18n)
147
+ end
148
+
149
+
150
+ module RequestMethods
151
+
152
+ # Obtains the locale from either ENV, HTTP (browser), Params or Session
153
+ # values.
154
+ #
155
+ # route do |r|
156
+ # # A): set from URL params ie: GET /posts?locale=de
157
+ # r.i18n_set_locale_from(:params)
158
+ #
159
+ # # B): set from session[:locale] (if present)
160
+ # r.i18n_set_locale_from(:session)
161
+ #
162
+ # # C): set from the browser's HTTP request locale
163
+ # r.i18n_set_locale_from(:http)
164
+ #
165
+ # # D): set from the server ENV['LANG'] variable
166
+ # r.i18n_set_locale_from(:ENV)
167
+ #
168
+ #
169
+ # r.is 'posts' do
170
+ # t.posts.header # use translations
171
+ # end
172
+ # end
173
+ #
174
+ def i18n_set_locale_from(type)
175
+ case type.to_sym
176
+ when :http
177
+ _locale = ::R18n::I18n.parse_http(scope.request.env['HTTP_ACCEPT_LANGUAGE'])
178
+ when :session
179
+ _locale = session[:locale] if session[:locale]
180
+ when :params
181
+ _locale = scope.request.params['locale'] if scope.request.params['locale']
182
+ when :ENV
183
+ _locale = ENV['LANG'].split('.').first if ENV['LANG']
184
+ else
185
+ _locale = nil
186
+ end
187
+ # sanity check: set to default locale if not set above
188
+ _locale = ::R18n::I18n.default.to_s if _locale.nil?
189
+
190
+ _i18n = ::R18n::I18n.new(_locale, ::R18n.default_places,
191
+ off_filters: :untranslated,
192
+ on_filters: :untranslated_html)
193
+ ::R18n.set(_i18n)
194
+ end
195
+
196
+ # Enables setting temporary :locale.
197
+ #
198
+ # route do |r|
199
+ #
200
+ # r.i18n_set_locale('de') do
201
+ # # within this block the locale is DE (German)
202
+ # end
203
+ #
204
+ # r.i18n_set_locale('es') do
205
+ # # within this block the locale is ES (Spanish)
206
+ # end
207
+ #
208
+ # end
209
+ #
210
+ def i18n_set_locale(locale, &blk)
211
+ locale = ::R18n::I18n.default.to_s if locale.nil?
212
+
213
+ _i18n = ::R18n::I18n.new(locale, ::R18n.default_places,
214
+ off_filters: :untranslated,
215
+ on_filters: :untranslated_html)
216
+ ::R18n.set(_i18n)
217
+ yield
218
+ end
219
+
220
+ # Sets the locale based upon <tt>:locale</tt> prefixed routes
221
+ #
222
+ # route do |r|
223
+ # r.locale do
224
+ # # all routes are prefixed with '/:locale'
225
+ # # ie: GET /de/posts => will use DE translations
226
+ # # ie: GET /es/posts => will use ES translations
227
+ # r.is 'posts' do
228
+ # t.posts.header # use translations or locales
229
+ # end
230
+ # end
231
+ # end
232
+ #
233
+ def locale(opts={}, &blk)
234
+ on(':locale', opts) do |l|
235
+ _locale = l || self.class.opts[:locale]
236
+ session[:locale] = _locale unless session[:locale]
237
+ ::R18n.set(_locale)
238
+ yield
239
+ end
240
+ end
241
+ alias_method :i18n_locale, :locale
242
+
243
+
244
+ end #/module RequestMethods
245
+
246
+ module ClassMethods
247
+
248
+ # Return the i18n options for this class.
249
+ def i18n_opts
250
+ opts[:i18n]
251
+ end
252
+
253
+ end #/module ClassMethods
254
+
255
+ module InstanceMethods
256
+ include ::R18n::Helpers
257
+
258
+ def i18n_available_locales
259
+ @available_locales = []
260
+ ::R18n.available_locales.each do |l|
261
+ @available_locales << [l.code, l.title]
262
+ end
263
+ @available_locales
264
+ end
265
+
266
+ def i18n_default_places
267
+ ::R18n.default_places
268
+ end
269
+
270
+ end
271
+
272
+ end #/module RodaI18n
273
+
274
+ register_plugin(:i18n, RodaI18n)
275
+ end #/module RodaPlugins
276
+ end #/class Roda