roda-i18n 0.4.0 → 0.6.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.
- checksums.yaml +5 -5
- data/.gitignore +3 -1
- data/CHANGELOG.md +25 -4
- data/Gemfile +16 -0
- data/Gemfile.lock +104 -0
- data/README.md +28 -97
- data/RELEASING.md +7 -0
- data/Rakefile +10 -14
- data/lib/roda/i18n/version.rb +3 -3
- data/lib/roda/i18n.rb +2 -0
- data/lib/roda/plugins/i18n.rb +141 -143
- data/roda-i18n.gemspec +26 -29
- metadata +20 -157
- data/.travis.yml +0 -11
data/lib/roda/plugins/i18n.rb
CHANGED
@@ -1,217 +1,211 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'r18n-core'
|
2
4
|
|
3
|
-
#
|
4
5
|
class Roda
|
5
|
-
|
6
|
-
#
|
7
6
|
module RodaPlugins
|
8
|
-
|
9
7
|
# The i18n plugin allows you to easily add internationalisation (i18n) and
|
10
8
|
# localisation support to your Roda app, by adding the following:
|
11
|
-
#
|
9
|
+
#
|
12
10
|
# plugin :i18n
|
13
|
-
#
|
14
|
-
# By default the default locale is set to <tt>'en'</tt> and the translations directory
|
11
|
+
#
|
12
|
+
# By default the default locale is set to <tt>'en'</tt> and the translations directory
|
15
13
|
# is set to <tt>'i18n'</tt> in the rooot of your app.
|
16
|
-
#
|
14
|
+
#
|
17
15
|
# Both <tt>:locale</tt> and <tt>:translations</tt> can be overridden during configuration:
|
18
|
-
#
|
16
|
+
#
|
19
17
|
# plugin :i18n, :locale => ['de'], :translations => ['absolute/path/2/i18n']
|
20
|
-
#
|
21
|
-
# Please note!
|
18
|
+
#
|
19
|
+
# Please note!
|
22
20
|
# 1) You must set +opts[:root]+ in your app if you don't define the +:translations+ path.
|
23
|
-
#
|
21
|
+
#
|
24
22
|
# 2) When overriding <tt>:translations</tt> the path given <b>must be absolute</b>.
|
25
|
-
#
|
23
|
+
#
|
26
24
|
# The path supports 'wildcards', ie: path/**/i18n so you can load translations from multiple
|
27
25
|
# combined apps each with their own <tt>i18n</tt> folder with translations.
|
28
|
-
#
|
29
|
-
# Note! when loading translations from multiple sources and the same translation key is used
|
30
|
-
# in both files, the first loaded file takes precedence, ie: <tt>./i18n/en.yml</tt> takes
|
26
|
+
#
|
27
|
+
# Note! when loading translations from multiple sources and the same translation key is used
|
28
|
+
# in both files, the first loaded file takes precedence, ie: <tt>./i18n/en.yml</tt> takes
|
31
29
|
# precedence over <tt>./apps/app1/i18n/en.yml</tt>
|
32
|
-
#
|
30
|
+
#
|
33
31
|
# == USAGE
|
34
|
-
#
|
32
|
+
#
|
35
33
|
# The i18n plugin depends upon simple YAML based translations files:
|
36
|
-
#
|
34
|
+
#
|
37
35
|
# # app/i18n/en.yml
|
38
|
-
#
|
36
|
+
#
|
39
37
|
# user:
|
40
38
|
# edit: Edit user
|
41
39
|
# name: User name is %1
|
42
40
|
# count: !!pl
|
43
41
|
# 1: There is 1 user
|
44
42
|
# n: There are %1 users
|
45
|
-
#
|
46
|
-
#
|
43
|
+
#
|
44
|
+
#
|
47
45
|
# and the <tt>:t</tt> instance method to output the translations:
|
48
|
-
#
|
49
|
-
#
|
46
|
+
#
|
47
|
+
#
|
50
48
|
# t.user.edit #=> "Edit user"
|
51
49
|
# t.user.name('John') #=> "User name is John"
|
52
50
|
# t.user.count(5) #=> "There are 5 users"
|
53
|
-
#
|
51
|
+
#
|
54
52
|
# t.does.not.exist | 'default' #=> "default"
|
55
|
-
#
|
56
|
-
#
|
53
|
+
#
|
54
|
+
#
|
57
55
|
# the <tt>:l</tt> instance method provides built-in localisations support:
|
58
|
-
#
|
56
|
+
#
|
59
57
|
# l Time.now #=> "03/01/2010 18:54"
|
60
58
|
# l Time.now, :human #=> "now"
|
61
59
|
# l Time.now, :full #=> "3rd of January, 2010 18:54"
|
62
|
-
#
|
60
|
+
#
|
63
61
|
# Both the +:t+ and +:l+ methods are available in the route and template (erb) scopes. ie:
|
64
|
-
#
|
62
|
+
#
|
65
63
|
# route do |r|
|
66
64
|
# r.root do
|
67
65
|
# t.welcome.message
|
68
66
|
# end
|
69
67
|
# end
|
70
|
-
#
|
68
|
+
#
|
71
69
|
# # app/views/layout.erb
|
72
70
|
# <snip...>
|
73
71
|
# <h1><%= t.welcome.message %></h1>
|
74
72
|
# <snip...>
|
75
|
-
#
|
76
|
-
#
|
77
|
-
#
|
73
|
+
#
|
74
|
+
#
|
75
|
+
#
|
78
76
|
# Visit [R18n](https://github.com/ai/r18n/tree/master/r18n-core) for more information.
|
79
|
-
#
|
80
|
-
#
|
77
|
+
#
|
78
|
+
#
|
81
79
|
# The i18n plugin also makes it easy to handle locales:
|
82
|
-
#
|
83
|
-
#
|
80
|
+
#
|
81
|
+
#
|
84
82
|
# === <tt>:locale</tt> RequestMethod
|
85
|
-
#
|
83
|
+
#
|
86
84
|
# This request method makes it to handle translations based upon the :locale prefix on a URL,
|
87
85
|
# ie: <tt>blog.com/de/posts</tt>, just use the following code:
|
88
|
-
#
|
86
|
+
#
|
89
87
|
# route do |r|
|
90
|
-
#
|
88
|
+
#
|
91
89
|
# r.locale do # or r.i18n_locale
|
92
|
-
# r.is 'posts' do
|
90
|
+
# r.is 'posts' do
|
93
91
|
# t.posts.header
|
94
92
|
# end
|
95
93
|
# end
|
96
|
-
#
|
94
|
+
#
|
97
95
|
# end
|
98
|
-
#
|
99
|
-
#
|
96
|
+
#
|
97
|
+
#
|
100
98
|
# === <tt>:i18n_set_locale_from</tt> RequestMethod
|
101
|
-
#
|
99
|
+
#
|
102
100
|
# Obtains the locale from either ENV, HTTP (browser), Params or Session values
|
103
|
-
#
|
104
|
-
#
|
101
|
+
#
|
102
|
+
#
|
105
103
|
# Naturally we can allow browsers to override the default locale within routes, like this:
|
106
|
-
#
|
104
|
+
#
|
107
105
|
# route do |r|
|
108
106
|
# i18n_set_locale_from(:http) #=> set to the browser's default locale (en-US)
|
109
107
|
# r.get '' do
|
110
108
|
# t.hello #=> 'Howdy, I speak American English'
|
111
109
|
# end
|
112
110
|
# end
|
113
|
-
#
|
111
|
+
#
|
114
112
|
# The def
|
115
|
-
#
|
116
|
-
#
|
113
|
+
#
|
114
|
+
#
|
117
115
|
# route do |r|
|
118
116
|
# i18n_set_locale('de')
|
119
117
|
# r.get 'in-german' do
|
120
118
|
# t.hello #=> 'Guten tag, ich spreche deutsch'
|
121
119
|
# end
|
122
120
|
# end
|
123
|
-
#
|
124
|
-
#
|
125
|
-
#
|
126
|
-
#
|
121
|
+
#
|
122
|
+
#
|
123
|
+
#
|
124
|
+
#
|
127
125
|
module RodaI18n
|
128
|
-
|
129
126
|
# default options
|
130
127
|
OPTS = {
|
131
128
|
# set the default locale
|
132
|
-
locale:
|
129
|
+
locale: 'en',
|
133
130
|
# set the default fallback locale
|
134
|
-
default_locale:
|
131
|
+
default_locale: 'en',
|
135
132
|
# set the default translations.
|
136
|
-
translations:
|
133
|
+
translations: nil
|
137
134
|
}.freeze
|
138
|
-
|
139
|
-
|
135
|
+
|
140
136
|
def self.configure(app, opts = OPTS)
|
141
|
-
if app.opts[:i18n]
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
137
|
+
opts = if app.opts[:i18n]
|
138
|
+
app.opts[:i18n][:orig_opts].merge(opts)
|
139
|
+
else
|
140
|
+
OPTS.merge(opts)
|
141
|
+
end
|
142
|
+
|
147
143
|
app.opts[:i18n] = opts.dup
|
148
144
|
app.opts[:i18n][:orig_opts] = opts
|
149
145
|
opts = app.opts[:i18n]
|
150
|
-
|
146
|
+
|
151
147
|
# set the translations path to defaults if nil
|
152
148
|
opts[:translations] = File.expand_path('i18n', app.opts[:root]) if opts[:translations].nil?
|
153
149
|
::R18n.default_places = opts[:translations]
|
154
|
-
|
155
|
-
# default_locale is either 'en' or the set value, so reset :default_locale if
|
150
|
+
|
151
|
+
# default_locale is either 'en' or the set value, so reset :default_locale if
|
156
152
|
# it is somehow nil or an empty string ' '
|
157
|
-
if opts[:default_locale].nil? || opts[:default_locale] =~ /^\s*$/
|
158
|
-
opts[:default_locale] = 'en'
|
159
|
-
end
|
153
|
+
opts[:default_locale] = 'en' if opts[:default_locale].nil? || opts[:default_locale] =~ /^\s*$/
|
160
154
|
::R18n::I18n.default = opts[:default_locale]
|
161
|
-
|
162
|
-
::R18n.clear_cache! if ENV['RACK_ENV'] != 'production'
|
163
|
-
|
164
|
-
|
155
|
+
# :nocov:
|
156
|
+
::R18n.clear_cache! if ENV['RACK_ENV'] != 'production' # nocov for simple one-liner
|
157
|
+
# :nocov:
|
158
|
+
i18n = R18n::I18n.new(
|
159
|
+
opts[:locale],
|
165
160
|
::R18n.default_places,
|
166
|
-
off_filters:
|
167
|
-
on_filters:
|
161
|
+
off_filters: :untranslated,
|
162
|
+
on_filters: :untranslated_html
|
168
163
|
)
|
169
164
|
::R18n.set(i18n)
|
170
165
|
end
|
171
|
-
|
166
|
+
|
172
167
|
# methods used within Roda's route block
|
173
|
-
#
|
168
|
+
#
|
174
169
|
module RequestMethods
|
175
|
-
|
176
170
|
# Obtains the locale from either ENV, HTTP (browser), Params or Session
|
177
171
|
# values.
|
178
|
-
#
|
172
|
+
#
|
179
173
|
# route do |r|
|
180
174
|
# # A): set from URL params ie: GET /posts?locale=de
|
181
175
|
# r.i18n_set_locale_from(:params)
|
182
|
-
#
|
176
|
+
#
|
183
177
|
# /url?locale=de
|
184
178
|
# <%= t.one %> #=> Ein
|
185
179
|
# /url?locale=es
|
186
180
|
# <%= t.one %> #=> Uno
|
187
|
-
#
|
181
|
+
#
|
188
182
|
# # B): set from session[:locale] (if present)
|
189
183
|
# r.i18n_set_locale_from(:session)
|
190
|
-
#
|
184
|
+
#
|
191
185
|
# session[:locale] = 'de'
|
192
186
|
# <%= t.one %> #=> Ein
|
193
187
|
# session[:locale] = 'es'
|
194
188
|
# <%= t.one %> #=> Uno
|
195
|
-
#
|
189
|
+
#
|
196
190
|
# # C): set from the browser's HTTP request locale
|
197
191
|
# r.i18n_set_locale_from(:http)
|
198
|
-
#
|
192
|
+
#
|
199
193
|
# HTTP_ACCEPT_LANGUAGE = 'sv-se;q=1,es;q=0.8,en;q=0.6'
|
200
194
|
# <%= t.one %> #=> Ett
|
201
|
-
#
|
195
|
+
#
|
202
196
|
# # D): set from the server ENV['LANG'] variable
|
203
197
|
# r.i18n_set_locale_from(:ENV)
|
204
|
-
#
|
198
|
+
#
|
205
199
|
# ENV['LANG'] = 'en_US.UTF8'
|
206
200
|
# <%= t.one %> #=> One
|
207
201
|
# ENV['LANG'] = 'es'
|
208
202
|
# <%= t.one %> #=> Uno
|
209
|
-
#
|
210
|
-
# r.is 'posts' do
|
203
|
+
#
|
204
|
+
# r.is 'posts' do
|
211
205
|
# t.posts.header # use translations
|
212
206
|
# end
|
213
207
|
# end
|
214
|
-
#
|
208
|
+
#
|
215
209
|
def i18n_set_locale_from(type)
|
216
210
|
case type.to_sym
|
217
211
|
when :http
|
@@ -219,7 +213,10 @@ class Roda
|
|
219
213
|
when :session
|
220
214
|
loc = session[:locale] if session[:locale]
|
221
215
|
when :params
|
216
|
+
# :nocov:
|
217
|
+
# loc will be nil, and caught after the `case` statement
|
222
218
|
loc = scope.request.params['locale'] if scope.request.params['locale']
|
219
|
+
# :nocov:
|
223
220
|
when :ENV
|
224
221
|
# ENV['LANG']="en_US.UTF-8"
|
225
222
|
loc = ENV['LANG'].split('.').first if ENV['LANG']
|
@@ -228,99 +225,102 @@ class Roda
|
|
228
225
|
end
|
229
226
|
# sanity check: set to default locale if not set above
|
230
227
|
loc = ::R18n::I18n.default.to_s if loc.nil?
|
231
|
-
|
228
|
+
|
232
229
|
i18n = ::R18n::I18n.new(
|
233
|
-
loc,
|
230
|
+
loc,
|
234
231
|
::R18n.default_places,
|
235
|
-
off_filters:
|
236
|
-
on_filters:
|
232
|
+
off_filters: :untranslated,
|
233
|
+
on_filters: :untranslated_html
|
237
234
|
)
|
238
235
|
::R18n.set(i18n)
|
239
236
|
end
|
240
|
-
|
237
|
+
|
241
238
|
# Enables setting temporary :locale blocks within the routing block.
|
242
|
-
#
|
239
|
+
#
|
243
240
|
# route do |r|
|
244
|
-
#
|
241
|
+
#
|
245
242
|
# r.i18n_set_locale('de') do
|
246
243
|
# # within this block the locale is DE (German)
|
247
244
|
# end
|
248
|
-
#
|
245
|
+
#
|
249
246
|
# r.i18n_set_locale('es') do
|
250
247
|
# # within this block the locale is ES (Spanish)
|
251
248
|
# end
|
252
|
-
#
|
249
|
+
#
|
253
250
|
# end
|
254
|
-
#
|
255
|
-
def i18n_set_locale(locale
|
256
|
-
|
257
|
-
|
251
|
+
#
|
252
|
+
def i18n_set_locale(locale)
|
253
|
+
sanitized_locale = i18n_set_locale_with_fallback(locale)
|
254
|
+
|
258
255
|
i18n = ::R18n::I18n.new(
|
259
|
-
|
260
|
-
::R18n.default_places,
|
261
|
-
off_filters:
|
262
|
-
on_filters:
|
256
|
+
sanitized_locale,
|
257
|
+
::R18n.default_places,
|
258
|
+
off_filters: :untranslated,
|
259
|
+
on_filters: :untranslated_html
|
263
260
|
)
|
264
261
|
::R18n.set(i18n)
|
265
262
|
yield if block_given?
|
266
|
-
# return # NB!! needed to enable routes below to work
|
267
263
|
end
|
268
|
-
|
264
|
+
|
269
265
|
# Match only paths that contain one available locale from the ::R18n.available_locales
|
270
266
|
# list, otherwise skip it.
|
271
267
|
#
|
272
268
|
# This custom matcher allows us to have other routes below the r.locale .. declaration
|
273
269
|
def _match_available_locales_only
|
274
|
-
|
275
|
-
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
|
280
|
-
|
270
|
+
lambda do
|
271
|
+
locale = remaining_path.split('/').reject(&:empty?).first.to_s
|
272
|
+
if ::R18n.available_locales.map { |locale| locale.code.downcase }.include?(locale.downcase)
|
273
|
+
@captures.push(locale)
|
274
|
+
@remaining_path = remaining_path.sub("/#{locale}", '')
|
275
|
+
end
|
276
|
+
end
|
281
277
|
end
|
282
|
-
|
278
|
+
|
283
279
|
# Sets the locale based upon <tt>:locale</tt> prefixed routes
|
284
|
-
#
|
280
|
+
#
|
285
281
|
# route do |r|
|
286
282
|
# r.locale do
|
287
283
|
# # all routes are prefixed with '/:locale'
|
288
284
|
# # ie: GET /de/posts => will use DE translations
|
289
285
|
# # ie: GET /es/posts => will use ES translations
|
290
|
-
# r.is 'posts' do
|
286
|
+
# r.is 'posts' do
|
291
287
|
# t.posts.header # use translations or locales
|
292
288
|
# end
|
293
289
|
# end
|
294
290
|
# r.get(:about) { erb(:about) }
|
295
291
|
# end
|
296
292
|
#
|
297
|
-
def locale(opts = {}
|
293
|
+
def locale(opts = {})
|
298
294
|
on(_match_available_locales_only, opts) do |l|
|
299
295
|
loc = l || Roda.opts[:locale]
|
300
296
|
::R18n.set(loc)
|
301
|
-
|
297
|
+
# :nocov:
|
298
|
+
yield loc if block_given? # nothing will happen without a block
|
302
299
|
return # NB!! needed to enable routes below to work
|
300
|
+
# :nocov:
|
303
301
|
end
|
304
302
|
end
|
305
|
-
|
306
|
-
|
307
|
-
|
308
|
-
|
309
|
-
|
303
|
+
alias i18n_locale locale
|
304
|
+
|
305
|
+
# Sets a fallback for locale => I18n.default
|
306
|
+
def i18n_set_locale_with_fallback(locale)
|
307
|
+
return ::R18n::I18n.default.to_s if locale.nil?
|
308
|
+
|
309
|
+
locale
|
310
|
+
end
|
311
|
+
end
|
312
|
+
|
310
313
|
module ClassMethods
|
311
|
-
|
312
314
|
# Return the i18n options for this plugin.
|
313
315
|
def i18n_opts
|
314
316
|
opts[:i18n]
|
315
317
|
end
|
316
|
-
|
317
|
-
|
318
|
-
|
319
|
-
|
318
|
+
end
|
319
|
+
|
320
320
|
# defines method available within the views / routing block
|
321
321
|
module InstanceMethods
|
322
322
|
include ::R18n::Helpers
|
323
|
-
|
323
|
+
|
324
324
|
def i18n_available_locales
|
325
325
|
@available_locales = []
|
326
326
|
::R18n.available_locales.each do |l|
|
@@ -328,15 +328,13 @@ class Roda
|
|
328
328
|
end
|
329
329
|
@available_locales
|
330
330
|
end
|
331
|
-
|
331
|
+
|
332
332
|
def i18n_default_places
|
333
333
|
::R18n.default_places
|
334
334
|
end
|
335
|
-
|
336
335
|
end
|
337
|
-
|
338
|
-
|
339
|
-
|
336
|
+
end
|
337
|
+
|
340
338
|
register_plugin(:i18n, RodaI18n)
|
341
|
-
end
|
342
|
-
end
|
339
|
+
end
|
340
|
+
end
|
data/roda-i18n.gemspec
CHANGED
@@ -1,19 +1,20 @@
|
|
1
|
-
#
|
2
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
lib = File.expand_path('lib', __dir__)
|
3
4
|
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
5
|
require 'roda/i18n/version'
|
5
6
|
|
6
7
|
Gem::Specification.new do |spec|
|
7
8
|
spec.name = 'roda-i18n'
|
8
|
-
spec.version =
|
9
|
+
spec.version = Roda::I18n::VERSION
|
9
10
|
spec.authors = ['Kematzy']
|
10
11
|
spec.email = ['kematzy@gmail.com']
|
11
|
-
|
12
|
+
|
12
13
|
spec.summary = 'Roda Internationalisation plugin'
|
13
|
-
spec.description =
|
14
|
+
spec.description = 'The Roda-i18n plugin enables easy addition of internationalisation (i18n) and localisation support in Roda apps'
|
14
15
|
spec.homepage = 'http://github.com/kematzy/roda-i18n'
|
15
16
|
spec.license = 'MIT'
|
16
|
-
|
17
|
+
|
17
18
|
# Prevent pushing this gem to RubyGems.org by setting 'allowed_push_host', or
|
18
19
|
# delete this section to allow pushing this gem to any host.
|
19
20
|
# if spec.respond_to?(:metadata)
|
@@ -22,29 +23,25 @@ Gem::Specification.new do |spec|
|
|
22
23
|
# raise "RubyGems 2.0 or newer is required to protect against public gem pushes."
|
23
24
|
# end
|
24
25
|
|
25
|
-
spec.files = `git ls-files -z`.split("\x0").reject
|
26
|
-
|
26
|
+
spec.files = `git ls-files -z`.split("\x0").reject do |f|
|
27
|
+
f.match(%r{^(test|spec|features|.github|.devcontainer)/}) ||
|
28
|
+
f.match(/^(Dockerfile|.tool-versions|justfile|.rubocop)/)
|
29
|
+
end
|
30
|
+
spec.bindir = 'exe'
|
27
31
|
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
28
|
-
spec.require_paths = [
|
29
|
-
|
32
|
+
spec.require_paths = ['lib']
|
33
|
+
|
30
34
|
spec.platform = Gem::Platform::RUBY
|
31
|
-
spec.
|
32
|
-
spec.
|
33
|
-
|
34
|
-
|
35
|
-
spec.
|
36
|
-
|
37
|
-
spec.
|
38
|
-
|
39
|
-
spec.
|
40
|
-
spec.
|
41
|
-
|
42
|
-
spec.
|
43
|
-
spec.add_development_dependency 'minitest', '~> 5.7', '>= 5.7.0'
|
44
|
-
spec.add_development_dependency 'minitest-hooks', '~> 1.1', '>= 1.1.0'
|
45
|
-
spec.add_development_dependency 'minitest-rg'
|
46
|
-
spec.add_development_dependency 'rack-test', '~> 1.0'
|
47
|
-
spec.add_development_dependency 'nokogiri'
|
48
|
-
spec.add_development_dependency 'simplecov'
|
49
|
-
|
35
|
+
spec.extra_rdoc_files = ['README.md', 'MIT-LICENSE']
|
36
|
+
spec.rdoc_options += ['--quiet', '--line-numbers', '--inline-source', '--title',
|
37
|
+
'Roda-i18n: internationalisation plugin', '--main', 'README.md']
|
38
|
+
|
39
|
+
spec.required_ruby_version = '>= 3.0.0'
|
40
|
+
|
41
|
+
spec.add_dependency 'date'
|
42
|
+
spec.add_dependency 'r18n-core', '~> 5'
|
43
|
+
spec.add_dependency 'roda', '~> 3.8'
|
44
|
+
spec.add_dependency 'tilt'
|
45
|
+
|
46
|
+
spec.metadata['rubygems_mfa_required'] = 'true'
|
50
47
|
end
|