fishman-i18n_routing 0.4.9
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.
- data/CHANGELOG.rdoc +99 -0
- data/Gemfile +3 -0
- data/Gemfile.lock +81 -0
- data/MIT-LICENSE +20 -0
- data/README.rdoc +32 -0
- data/Rakefile +28 -0
- data/init.rb +2 -0
- data/lib/i18n_routing.rb +8 -0
- data/lib/i18n_routing_common.rb +52 -0
- data/lib/i18n_routing_rails2.rb +237 -0
- data/lib/i18n_routing_rails3.rb +392 -0
- data/lib/i18n_routing_rails3.rb.orig +388 -0
- data/spec/i18n_routing/i18n_spec.rb +397 -0
- data/spec/locales/en.yml +15 -0
- data/spec/locales/fr.yml +36 -0
- data/spec/locales/pt-br.yml +9 -0
- data/spec/spec_helper.rb +44 -0
- metadata +82 -0
@@ -0,0 +1,392 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
require 'rack/mount'
|
3
|
+
require 'action_dispatch'
|
4
|
+
require 'active_support/core_ext/module'
|
5
|
+
|
6
|
+
module I18nRouting
|
7
|
+
module Mapper
|
8
|
+
|
9
|
+
private
|
10
|
+
|
11
|
+
# Just create a Mapper:Resource with given parameters
|
12
|
+
def resource_from_params(type, *resources)
|
13
|
+
res = resources.clone
|
14
|
+
|
15
|
+
options = res.extract_options!
|
16
|
+
r = res.first
|
17
|
+
|
18
|
+
type == :resource ? ActionDispatch::Routing::Mapper::SingletonResource.new(r, options.dup) : ActionDispatch::Routing::Mapper::Resource.new(r, options.dup)
|
19
|
+
end
|
20
|
+
|
21
|
+
# Localize a resources or a resource
|
22
|
+
def localized_resources(type = :resources, *resources, &block)
|
23
|
+
localizable_route = nil
|
24
|
+
|
25
|
+
if @locales
|
26
|
+
res = resources.clone
|
27
|
+
|
28
|
+
options = res.extract_options!
|
29
|
+
r = res.first
|
30
|
+
|
31
|
+
resource = resource_from_params(type, r, options.dup)
|
32
|
+
|
33
|
+
# Check for translated resource
|
34
|
+
stored_locale = I18n.locale
|
35
|
+
@locales.each do |locale|
|
36
|
+
I18n.locale = locale
|
37
|
+
localized_path = I18nRouting.translation_for(resource.name, type)
|
38
|
+
|
39
|
+
# A translated route exists :
|
40
|
+
if !localized_path.blank? and String === localized_path
|
41
|
+
puts("[I18n] > localize %-10s: %40s (%s) => /%s" % [type, resource.name, locale, localized_path]) if @i18n_verbose
|
42
|
+
opts = options.dup
|
43
|
+
opts[:path] = localized_path
|
44
|
+
opts[:controller] ||= r.to_s.pluralize
|
45
|
+
|
46
|
+
resource = resource_from_params(type, r, opts.dup)
|
47
|
+
|
48
|
+
res = ["#{I18nRouting.locale_escaped(locale)}_#{r}".to_sym, opts]
|
49
|
+
|
50
|
+
constraints = opts[:constraints] ? opts[:constraints].dup : {}
|
51
|
+
constraints[:i18n_locale] = locale.to_s
|
52
|
+
|
53
|
+
scope(:constraints => constraints, :path_names => I18nRouting.path_names(resource.name, @scope)) do
|
54
|
+
localized_branch(locale) do
|
55
|
+
send(type, *res) do
|
56
|
+
|
57
|
+
# In the resource(s) block, we need to keep and restore some context :
|
58
|
+
if block
|
59
|
+
old_name = @scope[:i18n_real_resource_name]
|
60
|
+
old = @scope[:scope_level_resource]
|
61
|
+
|
62
|
+
@scope[:i18n_real_resource_name] = resource.name
|
63
|
+
@scope[:i18n_scope_level_resource] = old
|
64
|
+
@scope[:scope_level_resource] = resource
|
65
|
+
|
66
|
+
if type == :resource and @scope[:name_prefix]
|
67
|
+
# Need to fake name_prefix for singleton resource
|
68
|
+
@scope[:name_prefix] = @scope[:name_prefix].gsub(Regexp.new("#{old.name}$"), resource.name)
|
69
|
+
end
|
70
|
+
|
71
|
+
block.call if block
|
72
|
+
|
73
|
+
@scope[:scope_level_resource] = old
|
74
|
+
@scope[:i18n_real_resource_name] = old_name
|
75
|
+
end
|
76
|
+
|
77
|
+
@scope[:i18n_scope_level_resource] = nil
|
78
|
+
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
localizable_route = resource
|
84
|
+
end
|
85
|
+
end
|
86
|
+
I18n.locale = stored_locale
|
87
|
+
end
|
88
|
+
return localizable_route
|
89
|
+
end
|
90
|
+
|
91
|
+
# Set if the next route created will be a localized route or not
|
92
|
+
# If yes, localizable is a name, or a Mapper::Resource
|
93
|
+
# Can take a block, if so, save the current context, set the new
|
94
|
+
# Call the block, then restore the old context and return the block return
|
95
|
+
def set_localizable_route(localizable)
|
96
|
+
if block_given?
|
97
|
+
old = @set.named_routes.localizable
|
98
|
+
@set.named_routes.set_localizable_route(localizable)
|
99
|
+
r = yield
|
100
|
+
@set.named_routes.set_localizable_route(old)
|
101
|
+
return r
|
102
|
+
else
|
103
|
+
@set.named_routes.set_localizable_route(localizable)
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
def localizable_route
|
108
|
+
@set.named_routes.localizable
|
109
|
+
end
|
110
|
+
|
111
|
+
# Return the aproximate deep in scope level
|
112
|
+
def nested_deep
|
113
|
+
(@scope and Array === @scope[:nested_deep] and @scope[:scope_level]) ? @scope[:nested_deep].size : 0
|
114
|
+
end
|
115
|
+
|
116
|
+
public
|
117
|
+
|
118
|
+
# On Routing::Mapper initialization (when doing Application.routes.draw do ...)
|
119
|
+
# prepare routing system to be i18n ready
|
120
|
+
def initialize(*args)
|
121
|
+
super
|
122
|
+
|
123
|
+
# Add i18n_locale as valid conditions for Rack::Mount / And add also :locale, as Rails 3.0.4 removed it ...
|
124
|
+
@valid_conditions = @set.instance_eval { @set }.instance_eval { @valid_conditions }
|
125
|
+
[:i18n_locale, :locale].each do |k|
|
126
|
+
@valid_conditions << k if !@valid_conditions.include?(k)
|
127
|
+
@set.valid_conditions << k if !@set.valid_conditions.include?(k)
|
128
|
+
end
|
129
|
+
|
130
|
+
# Extends the current RouteSet in order to define localized helper for named routes
|
131
|
+
# When calling define_url_helper, it calls define_localized_url_helper too.
|
132
|
+
if !@set.named_routes.respond_to?(:define_localized_url_helper)
|
133
|
+
@set.named_routes.class_eval <<-END_EVAL, __FILE__, __LINE__ + 1
|
134
|
+
alias_method :localized_define_url_helper, :define_url_helper
|
135
|
+
def define_url_helper(route, name, kind, options)
|
136
|
+
localized_define_url_helper(route, name, kind, options)
|
137
|
+
define_localized_url_helper(route, name, kind, options)
|
138
|
+
end
|
139
|
+
END_EVAL
|
140
|
+
|
141
|
+
@set.named_routes.extend I18nRouting::NamedRouteCollection
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
# Rails 3 routing system
|
146
|
+
# Create a block for localized routes, in your routes.rb :
|
147
|
+
#
|
148
|
+
# localized do
|
149
|
+
# resources :users
|
150
|
+
# match 'about' => 'contents#about', :as => :about
|
151
|
+
# end
|
152
|
+
#
|
153
|
+
def localized(locales = I18n.available_locales, opts = {})
|
154
|
+
# Add if not added Rails.root/config/locales/*.yml in the I18n.load_path
|
155
|
+
if !@i18n_routing_path_set and defined?(Rails) and Rails.respond_to?(:root) and Rails.root
|
156
|
+
I18n.load_path = (I18n.load_path << Dir[Rails.root.join('config', 'locales', '*.yml')]).flatten.uniq
|
157
|
+
@i18n_routing_path_set = true
|
158
|
+
end
|
159
|
+
|
160
|
+
old_value = @locales
|
161
|
+
@locales = locales
|
162
|
+
@i18n_verbose ||= opts.delete(:verbose)
|
163
|
+
yield
|
164
|
+
ensure
|
165
|
+
@locales = old_value
|
166
|
+
end
|
167
|
+
|
168
|
+
# Create a branch for create routes in the specified locale
|
169
|
+
def localized_branch(locale)
|
170
|
+
set_localizable_route(nil) do
|
171
|
+
old = @localized_branch
|
172
|
+
@localized_branch = locale
|
173
|
+
localized([locale]) do
|
174
|
+
yield
|
175
|
+
end
|
176
|
+
@localized_branch = old
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
180
|
+
# Set we do not want to localize next resource
|
181
|
+
def skip_localization
|
182
|
+
old = @skip_localization
|
183
|
+
@skip_localization = @localized_branch ? nil : true
|
184
|
+
yield
|
185
|
+
@skip_localization = old
|
186
|
+
end
|
187
|
+
|
188
|
+
def match(*args)
|
189
|
+
# Localize simple match only if there is no resource scope.
|
190
|
+
if args.size == 1 and @locales and !parent_resource and args.last.is_a?(Hash) and args.first[:as]
|
191
|
+
options = Marshal.load(Marshal.dump(args.first)) # Dump is dirty but how to make deep cloning easily ? :/
|
192
|
+
path, to = options.find { |name, value| name.is_a?(String) }
|
193
|
+
options.merge!(:to => to).delete(path)
|
194
|
+
@locales.each do |locale|
|
195
|
+
mapping = LocalizedMapping.new(locale, @set, @scope, path, options)
|
196
|
+
if mapping.localizable?
|
197
|
+
puts("[I18n] > localize %-10s: %40s (%s) => %s" % ['route', args.first[:as], locale, mapping.path]) if @i18n_verbose
|
198
|
+
@set.add_route(*mapping.to_route)
|
199
|
+
end
|
200
|
+
end
|
201
|
+
|
202
|
+
# Now, create the real match :
|
203
|
+
return set_localizable_route(args.first[:as]) do
|
204
|
+
super
|
205
|
+
end
|
206
|
+
end
|
207
|
+
|
208
|
+
super
|
209
|
+
end
|
210
|
+
|
211
|
+
def create_globalized_resources(type, *resources, &block)
|
212
|
+
#puts "#{' ' * nested_deep}Call #{type} : #{resources.inspect} (#{@locales.inspect}) (#{@localized_branch}) (#{@skip_localization})"
|
213
|
+
|
214
|
+
@scope[:nested_deep] ||= []
|
215
|
+
@scope[:nested_deep] << 1
|
216
|
+
|
217
|
+
cur_scope = nil
|
218
|
+
if @locales
|
219
|
+
localized = localized_resources(type, *resources, &block) if !@skip_localization
|
220
|
+
|
221
|
+
## We do not translate if we are in a translation branch :
|
222
|
+
return if localized and nested_deep > 0
|
223
|
+
|
224
|
+
# Set the current standard resource in order to customize url helper :
|
225
|
+
if !@localized_branch
|
226
|
+
r = resource_from_params(type, *resources)
|
227
|
+
cur_scope = (parent_resource and parent_resource.name == r.name) ? parent_resource : r
|
228
|
+
end
|
229
|
+
end
|
230
|
+
|
231
|
+
set_localizable_route(cur_scope) do
|
232
|
+
skip_localization do
|
233
|
+
#puts "#{' ' * nested_deep} \\- Call original #{type} : for #{resources.inspect}}"
|
234
|
+
send("#{type}_without_i18n_routing".to_sym, *resources, &block)
|
235
|
+
end
|
236
|
+
end
|
237
|
+
|
238
|
+
@scope[:nested_deep].pop
|
239
|
+
end
|
240
|
+
|
241
|
+
# Alias methods in order to handle i18n routes
|
242
|
+
def self.included(mod)
|
243
|
+
mod.send :alias_method_chain, :resource, :i18n_routing
|
244
|
+
mod.send :alias_method_chain, :resources, :i18n_routing
|
245
|
+
|
246
|
+
# Here we redefine some methods, in order to handle
|
247
|
+
# correct path_names translation on the fly
|
248
|
+
[:map_method, :member, :collection].each do |m|
|
249
|
+
rfname = "#{m}_without_i18n_routing".to_sym
|
250
|
+
mod.send :define_method, "#{m}_with_i18n_routing".to_sym do |*args, &block|
|
251
|
+
if @localized_branch and @scope[:i18n_scope_level_resource] and @scope[:i18n_real_resource_name]
|
252
|
+
o = @scope[:scope_level_resource]
|
253
|
+
@scope[:scope_level_resource] = @scope[:i18n_scope_level_resource]
|
254
|
+
|
255
|
+
pname = @scope[:path_names] || {}
|
256
|
+
i = 1
|
257
|
+
while i < args.size and (String === args[i] or Symbol === args[i])
|
258
|
+
pname[args[i]] = args[i]
|
259
|
+
i += 1
|
260
|
+
end
|
261
|
+
scope(:path_names => I18nRouting.path_names(@scope[:i18n_real_resource_name], {:path_names => pname})) do
|
262
|
+
send(rfname, *args, &block)
|
263
|
+
end
|
264
|
+
@scope[:scope_level_resource] = o
|
265
|
+
return
|
266
|
+
end
|
267
|
+
|
268
|
+
send(rfname, *args, &block)
|
269
|
+
end
|
270
|
+
|
271
|
+
mod.send :alias_method_chain, m, :i18n_routing
|
272
|
+
end
|
273
|
+
end
|
274
|
+
|
275
|
+
def resource_with_i18n_routing(*resources, &block)
|
276
|
+
create_globalized_resources(:resource, *resources, &block)
|
277
|
+
end
|
278
|
+
|
279
|
+
def resources_with_i18n_routing(*resources, &block)
|
280
|
+
create_globalized_resources(:resources, *resources, &block)
|
281
|
+
end
|
282
|
+
end
|
283
|
+
|
284
|
+
# Used for localize simple named routes
|
285
|
+
class LocalizedMapping < ActionDispatch::Routing::Mapper::Mapping
|
286
|
+
attr_reader :path
|
287
|
+
|
288
|
+
def initialize(locale, set, scope, path, options)
|
289
|
+
super(set, scope, path.clone, options ? options.clone : nil)
|
290
|
+
stored_locale = I18n.locale
|
291
|
+
|
292
|
+
# try to get translated path :
|
293
|
+
I18n.locale = locale
|
294
|
+
ts = @path.gsub(/^\//, '')
|
295
|
+
ts.gsub!('(.:format)', '')
|
296
|
+
|
297
|
+
tp = @options[:as] && I18nRouting.translation_for(@options[:as], :named_routes) ||
|
298
|
+
!ts.blank? && I18nRouting.translation_for(ts, :named_routes_path) || ts
|
299
|
+
|
300
|
+
@localized_path = File.join((@scope[:path] || ''), tp).gsub(/\/$/, '')
|
301
|
+
|
302
|
+
# If a translated path exists, set localized infos
|
303
|
+
if !@localized_path.blank? and @localized_path != @path
|
304
|
+
#@options[:controller] ||= @options[:as]
|
305
|
+
@options[:as] = "#{I18nRouting.locale_escaped(locale)}_#{@options[:as]}"
|
306
|
+
@path = @localized_path
|
307
|
+
@options[:constraints] = @options[:constraints] ? @options[:constraints].dup : {}
|
308
|
+
@options[:constraints][:i18n_locale] = locale.to_s
|
309
|
+
@options[:anchor] = true
|
310
|
+
# Force the recomputation of the requirements with the new values
|
311
|
+
@requirements = nil
|
312
|
+
else
|
313
|
+
@localized_path = nil
|
314
|
+
end
|
315
|
+
I18n.locale = stored_locale
|
316
|
+
end
|
317
|
+
|
318
|
+
# Return true if this route is localizable
|
319
|
+
def localizable?
|
320
|
+
@localized_path != nil
|
321
|
+
end
|
322
|
+
end
|
323
|
+
|
324
|
+
module NamedRouteCollection
|
325
|
+
attr_reader :localizable
|
326
|
+
|
327
|
+
def set_localizable_route(localizable)
|
328
|
+
@localizable = localizable
|
329
|
+
end
|
330
|
+
|
331
|
+
# Alias named route helper in order to check if a localized helper exists
|
332
|
+
# If not use the standard one.
|
333
|
+
def define_localized_url_helper(route, name, kind, options)
|
334
|
+
if n = localizable
|
335
|
+
selector = url_helper_name(name, kind)
|
336
|
+
|
337
|
+
rlang = if n.kind_of?(ActionDispatch::Routing::Mapper::Resources::Resource) and i = name.to_s.rindex("_#{n.plural}")
|
338
|
+
"#{selector.to_s[0, i]}_glang_#{n.plural}#{selector.to_s[i + "_#{n.plural}".size, selector.to_s.size]}"
|
339
|
+
elsif n.kind_of?(ActionDispatch::Routing::Mapper::Resources::Resource) and i = name.to_s.rindex("_#{n.singular}")
|
340
|
+
"#{selector.to_s[0, i]}_glang_#{n.singular}#{selector.to_s[i + "_#{n.singular}".size, selector.to_s.size]}"
|
341
|
+
else
|
342
|
+
"glang_#{selector}"
|
343
|
+
end
|
344
|
+
|
345
|
+
@module.module_eval <<-end_eval # We use module_eval to avoid leaks
|
346
|
+
alias_method :localized_#{selector}, :#{selector}
|
347
|
+
|
348
|
+
def #{selector}(*args)
|
349
|
+
selector_g = '#{rlang}'.gsub('glang', I18nRouting.locale_escaped(I18n.locale.to_s)).to_sym
|
350
|
+
|
351
|
+
#puts "Call routes : #{selector} => \#{selector_g} (\#{I18n.locale}) "
|
352
|
+
if respond_to? selector_g and selector_g != :#{selector}
|
353
|
+
send(selector_g, *args)
|
354
|
+
else
|
355
|
+
localized_#{selector}(*args)
|
356
|
+
end
|
357
|
+
end
|
358
|
+
|
359
|
+
end_eval
|
360
|
+
|
361
|
+
end
|
362
|
+
end
|
363
|
+
end
|
364
|
+
|
365
|
+
# Rack::Mount::Route module
|
366
|
+
# Exists in order to use apropriate localized route when using url_for
|
367
|
+
module RackMountRoute
|
368
|
+
# Alias methods in order to handle i18n routes
|
369
|
+
def self.included(mod)
|
370
|
+
mod.send :alias_method_chain, :generate, :i18n_routing
|
371
|
+
mod.send :alias_method_chain, :initialize, :i18n_routing
|
372
|
+
end
|
373
|
+
|
374
|
+
# During route initialization, if a condition i18n_locale is present
|
375
|
+
# Delete it, and store it in @locale
|
376
|
+
def initialize_with_i18n_routing(app, conditions, defaults, name)
|
377
|
+
@locale = conditions[:i18n_locale] ? conditions.delete(:i18n_locale).source.to_sym : nil
|
378
|
+
initialize_without_i18n_routing(app, conditions, defaults, name)
|
379
|
+
end
|
380
|
+
|
381
|
+
# Called for dynamic route generation
|
382
|
+
# If a @locale is present and if this locale is not the current one
|
383
|
+
# => return nil and refuse to generate the route
|
384
|
+
def generate_with_i18n_routing(method, params = {}, recall = {}, options = {})
|
385
|
+
return nil if @locale and @locale != I18n.locale.to_sym
|
386
|
+
generate_without_i18n_routing(method, params, recall, options)
|
387
|
+
end
|
388
|
+
end
|
389
|
+
end
|
390
|
+
|
391
|
+
ActionDispatch::Routing::Mapper.send :include, I18nRouting::Mapper
|
392
|
+
Rack::Mount::Route.send :include, I18nRouting::RackMountRoute
|
@@ -0,0 +1,388 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
require 'rack/mount'
|
3
|
+
require 'action_dispatch'
|
4
|
+
require 'active_support/core_ext/module'
|
5
|
+
|
6
|
+
module I18nRouting
|
7
|
+
module Mapper
|
8
|
+
|
9
|
+
private
|
10
|
+
|
11
|
+
# Just create a Mapper:Resource with given parameters
|
12
|
+
def resource_from_params(type, *resources)
|
13
|
+
res = resources.clone
|
14
|
+
|
15
|
+
options = res.extract_options!
|
16
|
+
r = res.first
|
17
|
+
|
18
|
+
type == :resource ? ActionDispatch::Routing::Mapper::SingletonResource.new(r, options.dup) : ActionDispatch::Routing::Mapper::Resource.new(r, options.dup)
|
19
|
+
end
|
20
|
+
|
21
|
+
# Localize a resources or a resource
|
22
|
+
def localized_resources(type = :resources, *resources, &block)
|
23
|
+
localizable_route = nil
|
24
|
+
|
25
|
+
if @locales
|
26
|
+
res = resources.clone
|
27
|
+
|
28
|
+
options = res.extract_options!
|
29
|
+
r = res.first
|
30
|
+
|
31
|
+
resource = resource_from_params(type, r, options.dup)
|
32
|
+
|
33
|
+
# Check for translated resource
|
34
|
+
@locales.each do |locale|
|
35
|
+
I18n.locale = locale
|
36
|
+
localized_path = I18nRouting.translation_for(resource.name, type)
|
37
|
+
|
38
|
+
# A translated route exists :
|
39
|
+
if !localized_path.blank? and String === localized_path
|
40
|
+
puts("[I18n] > localize %-10s: %40s (%s) => /%s" % [type, resource.name, locale, localized_path]) if @i18n_verbose
|
41
|
+
opts = options.dup
|
42
|
+
opts[:path] = localized_path
|
43
|
+
opts[:controller] ||= r.to_s.pluralize
|
44
|
+
|
45
|
+
resource = resource_from_params(type, r, opts.dup)
|
46
|
+
|
47
|
+
res = ["#{I18nRouting.locale_escaped(locale)}_#{r}".to_sym, opts]
|
48
|
+
|
49
|
+
constraints = opts[:constraints] ? opts[:constraints].dup : {}
|
50
|
+
constraints[:i18n_locale] = locale.to_s
|
51
|
+
|
52
|
+
scope(:constraints => constraints, :path_names => I18nRouting.path_names(resource.name, @scope)) do
|
53
|
+
localized_branch(locale) do
|
54
|
+
send(type, *res) do
|
55
|
+
|
56
|
+
# In the resource(s) block, we need to keep and restore some context :
|
57
|
+
if block
|
58
|
+
old_name = @scope[:i18n_real_resource_name]
|
59
|
+
old = @scope[:scope_level_resource]
|
60
|
+
|
61
|
+
@scope[:i18n_real_resource_name] = resource.name
|
62
|
+
@scope[:i18n_scope_level_resource] = old
|
63
|
+
@scope[:scope_level_resource] = resource
|
64
|
+
|
65
|
+
if type == :resource and @scope[:name_prefix]
|
66
|
+
# Need to fake name_prefix for singleton resource
|
67
|
+
@scope[:name_prefix] = @scope[:name_prefix].gsub(Regexp.new("#{old.name}$"), resource.name)
|
68
|
+
end
|
69
|
+
|
70
|
+
block.call if block
|
71
|
+
|
72
|
+
@scope[:scope_level_resource] = old
|
73
|
+
@scope[:i18n_real_resource_name] = old_name
|
74
|
+
end
|
75
|
+
|
76
|
+
@scope[:i18n_scope_level_resource] = nil
|
77
|
+
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
localizable_route = resource
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
return localizable_route
|
87
|
+
end
|
88
|
+
|
89
|
+
# Set if the next route created will be a localized route or not
|
90
|
+
# If yes, localizable is a name, or a Mapper::Resource
|
91
|
+
# Can take a block, if so, save the current context, set the new
|
92
|
+
# Call the block, then restore the old context and return the block return
|
93
|
+
def set_localizable_route(localizable)
|
94
|
+
if block_given?
|
95
|
+
old = @set.named_routes.localizable
|
96
|
+
@set.named_routes.set_localizable_route(localizable)
|
97
|
+
r = yield
|
98
|
+
@set.named_routes.set_localizable_route(old)
|
99
|
+
return r
|
100
|
+
else
|
101
|
+
@set.named_routes.set_localizable_route(localizable)
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
def localizable_route
|
106
|
+
@set.named_routes.localizable
|
107
|
+
end
|
108
|
+
|
109
|
+
# Return the aproximate deep in scope level
|
110
|
+
def nested_deep
|
111
|
+
(@scope and Array === @scope[:nested_deep] and @scope[:scope_level]) ? @scope[:nested_deep].size : 0
|
112
|
+
end
|
113
|
+
|
114
|
+
public
|
115
|
+
|
116
|
+
# On Routing::Mapper initialization (when doing Application.routes.draw do ...)
|
117
|
+
# prepare routing system to be i18n ready
|
118
|
+
def initialize(*args)
|
119
|
+
super
|
120
|
+
|
121
|
+
# Add i18n_locale as valid conditions for Rack::Mount / And add also :locale, as Rails 3.0.4 removed it ...
|
122
|
+
@valid_conditions = @set.instance_eval { @set }.instance_eval { @valid_conditions }
|
123
|
+
[:i18n_locale, :locale].each do |k|
|
124
|
+
@valid_conditions << k if !@valid_conditions.include?(k)
|
125
|
+
@set.valid_conditions << k if !@set.valid_conditions.include?(k)
|
126
|
+
end
|
127
|
+
|
128
|
+
# Extends the current RouteSet in order to define localized helper for named routes
|
129
|
+
# When calling define_url_helper, it calls define_localized_url_helper too.
|
130
|
+
if !@set.named_routes.respond_to?(:define_localized_url_helper)
|
131
|
+
@set.named_routes.class_eval <<-END_EVAL, __FILE__, __LINE__ + 1
|
132
|
+
alias_method :localized_define_url_helper, :define_url_helper
|
133
|
+
def define_url_helper(route, name, kind, options)
|
134
|
+
localized_define_url_helper(route, name, kind, options)
|
135
|
+
define_localized_url_helper(route, name, kind, options)
|
136
|
+
end
|
137
|
+
END_EVAL
|
138
|
+
|
139
|
+
@set.named_routes.extend I18nRouting::NamedRouteCollection
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
# Rails 3 routing system
|
144
|
+
# Create a block for localized routes, in your routes.rb :
|
145
|
+
#
|
146
|
+
# localized do
|
147
|
+
# resources :users
|
148
|
+
# match 'about' => 'contents#about', :as => :about
|
149
|
+
# end
|
150
|
+
#
|
151
|
+
def localized(locales = I18n.available_locales, opts = {})
|
152
|
+
# Add if not added Rails.root/config/locales/*.yml in the I18n.load_path
|
153
|
+
if !@i18n_routing_path_set and defined?(Rails) and Rails.respond_to?(:root) and Rails.root
|
154
|
+
I18n.load_path = (I18n.load_path << Dir[Rails.root.join('config', 'locales', '*.yml')]).flatten.uniq
|
155
|
+
@i18n_routing_path_set = true
|
156
|
+
end
|
157
|
+
|
158
|
+
old_value = @locales
|
159
|
+
@locales = locales
|
160
|
+
@i18n_verbose ||= opts.delete(:verbose)
|
161
|
+
yield
|
162
|
+
ensure
|
163
|
+
@locales = old_value
|
164
|
+
end
|
165
|
+
|
166
|
+
# Create a branch for create routes in the specified locale
|
167
|
+
def localized_branch(locale)
|
168
|
+
set_localizable_route(nil) do
|
169
|
+
old = @localized_branch
|
170
|
+
@localized_branch = locale
|
171
|
+
localized([locale]) do
|
172
|
+
yield
|
173
|
+
end
|
174
|
+
@localized_branch = old
|
175
|
+
end
|
176
|
+
end
|
177
|
+
|
178
|
+
# Set we do not want to localize next resource
|
179
|
+
def skip_localization
|
180
|
+
old = @skip_localization
|
181
|
+
@skip_localization = @localized_branch ? nil : true
|
182
|
+
yield
|
183
|
+
@skip_localization = old
|
184
|
+
end
|
185
|
+
|
186
|
+
def match(*args)
|
187
|
+
# Localize simple match only if there is no resource scope.
|
188
|
+
if args.size == 1 and @locales and !parent_resource and args.last.is_a?(Hash) and args.first[:as]
|
189
|
+
options = Marshal.load(Marshal.dump(args.first)) # Dump is dirty but how to make deep cloning easily ? :/
|
190
|
+
path, to = options.find { |name, value| name.is_a?(String) }
|
191
|
+
options.merge!(:to => to).delete(path)
|
192
|
+
@locales.each do |locale|
|
193
|
+
mapping = LocalizedMapping.new(locale, @set, @scope, path, options)
|
194
|
+
if mapping.localizable?
|
195
|
+
puts("[I18n] > localize %-10s: %40s (%s) => %s" % ['route', args.first[:as], locale, mapping.path]) if @i18n_verbose
|
196
|
+
@set.add_route(*mapping.to_route)
|
197
|
+
end
|
198
|
+
end
|
199
|
+
|
200
|
+
# Now, create the real match :
|
201
|
+
return set_localizable_route(args.first[:as]) do
|
202
|
+
super
|
203
|
+
end
|
204
|
+
end
|
205
|
+
|
206
|
+
super
|
207
|
+
end
|
208
|
+
|
209
|
+
def create_globalized_resources(type, *resources, &block)
|
210
|
+
#puts "#{' ' * nested_deep}Call #{type} : #{resources.inspect} (#{@locales.inspect}) (#{@localized_branch}) (#{@skip_localization})"
|
211
|
+
|
212
|
+
@scope[:nested_deep] ||= []
|
213
|
+
@scope[:nested_deep] << 1
|
214
|
+
|
215
|
+
cur_scope = nil
|
216
|
+
if @locales
|
217
|
+
localized = localized_resources(type, *resources, &block) if !@skip_localization
|
218
|
+
|
219
|
+
## We do not translate if we are in a translation branch :
|
220
|
+
return if localized and nested_deep > 0
|
221
|
+
|
222
|
+
# Set the current standard resource in order to customize url helper :
|
223
|
+
if !@localized_branch
|
224
|
+
r = resource_from_params(type, *resources)
|
225
|
+
cur_scope = (parent_resource and parent_resource.name == r.name) ? parent_resource : r
|
226
|
+
end
|
227
|
+
end
|
228
|
+
|
229
|
+
set_localizable_route(cur_scope) do
|
230
|
+
skip_localization do
|
231
|
+
#puts "#{' ' * nested_deep} \\- Call original #{type} : for #{resources.inspect}}"
|
232
|
+
send("#{type}_without_i18n_routing".to_sym, *resources, &block)
|
233
|
+
end
|
234
|
+
end
|
235
|
+
|
236
|
+
@scope[:nested_deep].pop
|
237
|
+
end
|
238
|
+
|
239
|
+
# Alias methods in order to handle i18n routes
|
240
|
+
def self.included(mod)
|
241
|
+
mod.send :alias_method_chain, :resource, :i18n_routing
|
242
|
+
mod.send :alias_method_chain, :resources, :i18n_routing
|
243
|
+
|
244
|
+
# Here we redefine some methods, in order to handle
|
245
|
+
# correct path_names translation on the fly
|
246
|
+
[:map_method, :member, :collection].each do |m|
|
247
|
+
rfname = "#{m}_without_i18n_routing".to_sym
|
248
|
+
mod.send :define_method, "#{m}_with_i18n_routing".to_sym do |*args, &block|
|
249
|
+
if @localized_branch and @scope[:i18n_scope_level_resource] and @scope[:i18n_real_resource_name]
|
250
|
+
o = @scope[:scope_level_resource]
|
251
|
+
@scope[:scope_level_resource] = @scope[:i18n_scope_level_resource]
|
252
|
+
|
253
|
+
pname = @scope[:path_names] || {}
|
254
|
+
i = 1
|
255
|
+
while i < args.size and (String === args[i] or Symbol === args[i])
|
256
|
+
pname[args[i]] = args[i]
|
257
|
+
i += 1
|
258
|
+
end
|
259
|
+
scope(:path_names => I18nRouting.path_names(@scope[:i18n_real_resource_name], {:path_names => pname})) do
|
260
|
+
send(rfname, *args, &block)
|
261
|
+
end
|
262
|
+
@scope[:scope_level_resource] = o
|
263
|
+
return
|
264
|
+
end
|
265
|
+
|
266
|
+
send(rfname, *args, &block)
|
267
|
+
end
|
268
|
+
|
269
|
+
mod.send :alias_method_chain, m, :i18n_routing
|
270
|
+
end
|
271
|
+
end
|
272
|
+
|
273
|
+
def resource_with_i18n_routing(*resources, &block)
|
274
|
+
create_globalized_resources(:resource, *resources, &block)
|
275
|
+
end
|
276
|
+
|
277
|
+
def resources_with_i18n_routing(*resources, &block)
|
278
|
+
create_globalized_resources(:resources, *resources, &block)
|
279
|
+
end
|
280
|
+
end
|
281
|
+
|
282
|
+
# Used for localize simple named routes
|
283
|
+
class LocalizedMapping < ActionDispatch::Routing::Mapper::Mapping
|
284
|
+
attr_reader :path
|
285
|
+
|
286
|
+
def initialize(locale, set, scope, path, options)
|
287
|
+
super(set, scope, path.clone, options ? options.clone : nil)
|
288
|
+
|
289
|
+
# try to get translated path :
|
290
|
+
I18n.locale = locale
|
291
|
+
ts = @path.gsub(/^\//, '')
|
292
|
+
ts.gsub!('(.:format)', '')
|
293
|
+
|
294
|
+
tp = @options[:as] && I18nRouting.translation_for(@options[:as], :named_routes) ||
|
295
|
+
!ts.blank? && I18nRouting.translation_for(ts, :named_routes_path) || ts
|
296
|
+
|
297
|
+
@localized_path = File.join((@scope[:path] || ''), tp).gsub(/\/$/, '')
|
298
|
+
|
299
|
+
# If a translated path exists, set localized infos
|
300
|
+
if !@localized_path.blank? and @localized_path != @path
|
301
|
+
#@options[:controller] ||= @options[:as]
|
302
|
+
@options[:as] = "#{I18nRouting.locale_escaped(locale)}_#{@options[:as]}"
|
303
|
+
@path = @localized_path
|
304
|
+
@options[:constraints] = @options[:constraints] ? @options[:constraints].dup : {}
|
305
|
+
@options[:constraints][:i18n_locale] = locale.to_s
|
306
|
+
@options[:anchor] = true
|
307
|
+
# Force the recomputation of the requirements with the new values
|
308
|
+
@requirements = nil
|
309
|
+
else
|
310
|
+
@localized_path = nil
|
311
|
+
end
|
312
|
+
end
|
313
|
+
|
314
|
+
# Return true if this route is localizable
|
315
|
+
def localizable?
|
316
|
+
@localized_path != nil
|
317
|
+
end
|
318
|
+
end
|
319
|
+
|
320
|
+
module NamedRouteCollection
|
321
|
+
attr_reader :localizable
|
322
|
+
|
323
|
+
def set_localizable_route(localizable)
|
324
|
+
@localizable = localizable
|
325
|
+
end
|
326
|
+
|
327
|
+
# Alias named route helper in order to check if a localized helper exists
|
328
|
+
# If not use the standard one.
|
329
|
+
def define_localized_url_helper(route, name, kind, options)
|
330
|
+
if n = localizable
|
331
|
+
selector = url_helper_name(name, kind)
|
332
|
+
|
333
|
+
rlang = if n.kind_of?(ActionDispatch::Routing::Mapper::Resources::Resource) and i = name.to_s.rindex("_#{n.plural}")
|
334
|
+
"#{selector.to_s[0, i]}_glang_#{n.plural}#{selector.to_s[i + "_#{n.plural}".size, selector.to_s.size]}"
|
335
|
+
elsif n.kind_of?(ActionDispatch::Routing::Mapper::Resources::Resource) and i = name.to_s.rindex("_#{n.singular}")
|
336
|
+
"#{selector.to_s[0, i]}_glang_#{n.singular}#{selector.to_s[i + "_#{n.singular}".size, selector.to_s.size]}"
|
337
|
+
else
|
338
|
+
"glang_#{selector}"
|
339
|
+
end
|
340
|
+
|
341
|
+
@module.module_eval <<-end_eval # We use module_eval to avoid leaks
|
342
|
+
alias_method :localized_#{selector}, :#{selector}
|
343
|
+
|
344
|
+
def #{selector}(*args)
|
345
|
+
selector_g = '#{rlang}'.gsub('glang', I18nRouting.locale_escaped(I18n.locale.to_s)).to_sym
|
346
|
+
|
347
|
+
#puts "Call routes : #{selector} => \#{selector_g} (\#{I18n.locale}) "
|
348
|
+
if respond_to? selector_g and selector_g != :#{selector}
|
349
|
+
send(selector_g, *args)
|
350
|
+
else
|
351
|
+
localized_#{selector}(*args)
|
352
|
+
end
|
353
|
+
end
|
354
|
+
|
355
|
+
end_eval
|
356
|
+
|
357
|
+
end
|
358
|
+
end
|
359
|
+
end
|
360
|
+
|
361
|
+
# Rack::Mount::Route module
|
362
|
+
# Exists in order to use apropriate localized route when using url_for
|
363
|
+
module RackMountRoute
|
364
|
+
# Alias methods in order to handle i18n routes
|
365
|
+
def self.included(mod)
|
366
|
+
mod.send :alias_method_chain, :generate, :i18n_routing
|
367
|
+
mod.send :alias_method_chain, :initialize, :i18n_routing
|
368
|
+
end
|
369
|
+
|
370
|
+
# During route initialization, if a condition i18n_locale is present
|
371
|
+
# Delete it, and store it in @locale
|
372
|
+
def initialize_with_i18n_routing(app, conditions, defaults, name)
|
373
|
+
@locale = conditions[:i18n_locale] ? conditions.delete(:i18n_locale).source.to_sym : nil
|
374
|
+
initialize_without_i18n_routing(app, conditions, defaults, name)
|
375
|
+
end
|
376
|
+
|
377
|
+
# Called for dynamic route generation
|
378
|
+
# If a @locale is present and if this locale is not the current one
|
379
|
+
# => return nil and refuse to generate the route
|
380
|
+
def generate_with_i18n_routing(method, params = {}, recall = {}, options = {})
|
381
|
+
return nil if @locale and @locale != I18n.locale.to_sym
|
382
|
+
generate_without_i18n_routing(method, params, recall, options)
|
383
|
+
end
|
384
|
+
end
|
385
|
+
end
|
386
|
+
|
387
|
+
ActionDispatch::Routing::Mapper.send :include, I18nRouting::Mapper
|
388
|
+
Rack::Mount::Route.send :include, I18nRouting::RackMountRoute
|